2

I'm trying to display alert messages (success, warning, danger) on the index.html page:

...
</header>
<div ng-controller="AlertController">
  <alert ng-repeat="alert in alerts" type="alert.status" close="closeAlert($index)">{{ alert.message }}</alert>
</div>
<div ng-view></div>
...

To do so I've written an AlertController in controllers.js. It might be a good idea to receive the alerts through $broadcast / $on.

Controllers.controller('AlertController', ['$scope', '$rootScope',
function ($scope, $rootScope) {
    $scope.alerts = [];

    $rootScope.$on('alert:success', function(message) {
        $scope.alerts.push({'status': 'success', 'message': message});
    });

    $rootScope.$on('alert:warning', function(message) {
        $scope.alerts.push({'status': 'warning', 'message': message});
    });

    $rootScope.$on('alert:danger', function(message) {
        $scope.alerts.push({'status': 'danger', 'message': message});
    });

    $scope.closeAlert = function (alert) {
        return this.closeAlertIndex($scope.alerts.indexOf(alert));
    };

    $scope.closeAlertIndex = function (index) {
        return $scope.alerts.splice(index, 1);
    };
}]);

But when I do use other Controllers the alerts do not get displayed:

Controllers.controller('LocationController', ['$scope', '$rootScope', '$routeParams', 'LocationService',
function ($scope, $rootScope, $routeParams, LocationService) {
    $scope.Location = {};
    $scope.Locations = [];

    $scope.queryLocation = function () {
        LocationService.query({active: $routeParams.active}, function (locations) {
            $scope.Locations = locations;
            $rootScope.$broadcast('alert:success', "Location queried: active = " + $routeParams.active);
            console.log("Location queried");
        }, function (error) {
            $rootScope.$broadcast('alert:warning', "Unable to query location: " + error.message);
            console.log("Unable to query location");
        });
    };

    $scope.getLocation = function () {
        LocationService.get({id: $routeParams.id}, function (location) {
            $scope.Location = location;
            $rootScope.$broadcast('alert:success', "Location got: " + location.id);
            console.log("Location got");
        }, function (error) {
            $rootScope.$broadcast('alert:warning', "Unable to get location: " + error.message);
            console.log("Unable to get location");
        });
    };
}]);

I can see the console.log() messages, but not the alerts. In the logs I do also see a 404 error on alert.html. Do I have to create an alert.html file to use the tag?

I did read some other posts where they suggested to use a service instead of a controller, but this didn't work on my page. Also I think it's a simple solution to just broadcast the alert...

How can I fix this?

Cheers

Thanks to the responses I did rewrite the code. I had to do the following to get it working:

  • The alert-tag did not work so I changed from ui-bootstrap.min.js to ui-bootstrap-tpls.min.js

  • New PubSubService based on glepretre's proposal

  • New AlertController:

    Controllers.controller('AlertController', ['$scope', 'PubSubService', function ($scope, PubSubService) { $scope.alerts = [];

    $scope.addAlert = function(status, message) {
        $scope.alerts.push({'status': status, 'message': message});
    };
    
    $scope.closeAlert = function(index) {
        $scope.alerts.splice(index, 1);
    };
    
    PubSubService.subscribe('alert', $scope.addAlert);}]);
    

Now I can add alerts using

PubSubService.publish('alert', 'warning', 'Unable to get location: ' + error.message);

But this solution is not using $broadcast.

decurgia
  • 111
  • 1
  • 9

1 Answers1

4

$broadcasts only go DOWN scopes and $emits go UP scopes. Use $emit combined with $rootScope. Since $rootScope is the top scope, all $emits will hit it. Additionally, I'd put that in a service instead of a controller, but I don't really know what you're doing.

Jonathan Rowny
  • 7,588
  • 1
  • 18
  • 26
  • So far I do have got 3 controllers (Location, Object, Event) and 1 directive (Google+SignIn). I do want to display success/warning/danger messages of those controllers/directives on top of
    . Looks like I've to give the service another try.
    – decurgia Feb 25 '14 at 17:28
  • Definitely move it to a service. The nature of events are fire-and-forget which this breaks by requiring that something occurs. Plus you don't really care about scopes when you want to display the alert, only the data sent. You'll find it's much easier for all of your objects to share an Alert service via dependency injection than it is to manipulate all the scope chains. – Matt Pileggi Feb 25 '14 at 17:42
  • Also please consider that `$rootScope.$emit` is a potential performance killer. Have a look http://stackoverflow.com/questions/11252780/whats-the-correct-way-to-communicate-between-controllers-in-angularjs and https://github.com/angular/angular.js/issues/4574 – Mr_Mig Feb 26 '14 at 14:38
  • @Mr_Mig I didn't mean to suggest using `$rootScope.$emit`, I meant to suggest using `$scope.$emit` from child scopes and catching them with `$rootScope.$on`. You're only bubling up, not broadcasting out, the performance impact should be acceptable. I would think a bubling event bus is still more performant than a bunch of watchers... but I have no stats to back that up. I do like the idea of using a pubsub service as described in one of the links you attached. – Jonathan Rowny Feb 26 '14 at 14:52
  • @JonathanRowny I do not like the events at all in such cases, cause they tend to create a mess (in maintenability perspective). I would go for the separate service with methods to trigger the alerts instead. This will allow to decouple the logic from the scope hierarchy at all! – Mr_Mig Feb 26 '14 at 15:06