r_bvhg
Last Updated: March 23, 2017
·
65.44K
· blrandel

Angular-UI Bootstrap alert service for Angular.js

Angular-UI Bootstrap provides a number of controls ported from the popular Bootstrap project to Angular's directives (with a noticeable reduction in code size). If you're planning on using any Bootstrap components in your Angular app, I'd highly recommend checking it out. That being said, this should work too if you're just including components directly from Bootstrap.

Services in Angular.js are intended for sharing code between controllers. Alerts are one of many good usecases for moving code out of a controller to a service.

The Angular-UI Bootstrap documentation provides the following examples:

view

<div ng-controller="AlertDemoCtrl">
  <alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{alert.msg}}</alert>
  <button class='btn' ng-click="addAlert()">Add Alert</button>
</div>

controller

function AlertDemoCtrl($scope) {
  $scope.alerts = [
    { type: 'error', msg: 'Oh snap! Change a few things up and try submitting again.' }, 
    { type: 'success', msg: 'Well done! You successfully read this important alert message.' }
  ];

  $scope.addAlert = function() {
    $scope.alerts.push({msg: "Another alert!"});
  };

  $scope.closeAlert = function(index) {
    $scope.alerts.splice(index, 1);
  };
}

Given that we're going to want to create alerts from different controllers in our app, and code references across controllers is considered bad practice, we're going to want to move this to a service.

alertService

'use strict';

/* services.js */

// don't forget to declare this service module as a dependency in your main app constructor!
var appServices = angular.module('appApp.services', []);

appServices.factory('alertService', function($rootScope) {
    var alertService = {};

    // create an array of alerts available globally
    $rootScope.alerts = [];

    alertService.add = function(type, msg) {
      $rootScope.alerts.push({'type': type, 'msg': msg});
    };

    alertService.closeAlert = function(index) {
      $rootScope.alerts.splice(index, 1);
    };

    return alertService;
  });

View

This markup will probably be in your index.html or included from a header template.

<div>
  <alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{ alert.msg }}</alert>
</div>

Finally, we need to bind the alertService's closeAlert() method to the $globalScope.

Controller

function RootCtrl($rootScope, $location, alertService) {
  $rootScope.changeView = function(view) {
    $location.path(view);
  }

  // root binding for alertService
  $rootScope.closeAlert = alertService.closeAlert; 
}
RootCtrl.$inject = ['$scope', '$location', 'alertService'];

I'm not entirely satisfied with this global binding, and would prefer being able to call the service method directly from the close data attribute on the alert directive, yet I couldn't see how to accomplish this.

Now creating an alert is just a matter of calling alertService.add() from any of your controllers.

function ArbitraryCtrl($scope, alertService) {
  alertService.add("warning", "This is a warning.");
  alertService.add("error", "This is an error!");
}

If anyone has any improvements to offer, please leave a comment. Hopefully this has been a useful introduction to Angular.js services.

21 Responses
Add your response

6454
Af3f411e9ded3f60427cfa1ccdb7bba8

The following would work without needing to inject a controller, you simply add a close method to the alert you are building:

.factory('AppAlert', ['$rootScope', ($rootScope) ->
      $rootScope.alerts = []
      alertService = 
        add: (type, msg) ->
          $rootScope.alerts.push { type: type, msg: msg, close: -> alertService.closeAlert(this) } 
        closeAlert: (alert) ->
          @closeAlertIdx $rootScope.alerts.indexOf(alert)
        closeAlertIdx: (index) ->
          $rootScope.alerts.splice index, 1
      ])

and in the alert directive simply replace the close to
close="alert.close()"

over 1 year ago ·
8999
B224170982470b1e61ed0ee34522c221

Thanks, this helped me a lot!

over 1 year ago ·
9035
8e9d1b8c822ad809dc19d334a7a3ce8f

Thanks Bayard and jwickers! Not only are my alerts finally closing, but it was nice seeing how you implemented and how jwickers improved. I'm very new to AngularJS, and examples like this are priceless.

over 1 year ago ·
9767
0 fquk8jq uj9d15lqttsi8xvmmukq15kqsnzf8yqaqykn23pn  yyao8hbzrmkc54acmm7j6q 9hq

Thanks Bayard! This looks like a good approach for dealing with alerts in Angular way.
It has been often said that we should avoid globals. I am newbee to Angular but was just wondering about using a Main Controller to define the methods to add/close Alerts.
And using the $scope and not $routeScope.

Please have a look at the following thread and provide your opinion.
http://stackoverflow.com/questions/17006054/angularjs-globalctrl-vs-rootscope-vs-service

over 1 year ago ·
9857

hi there

thanks a lot for providing this sample. i'm a bloody beginner n javascript/html5 and right now i'm trying to implement the version from jwickers.
i can't get it running. struggling on the "->" operator in the service. couldn't fin this operator in the javascript syntax. can somebody help or maybe even upload a finished sample?
thanks for any enlightment

over 1 year ago ·
10348
0 iwkw8jyxczfre0cqiuhn8ydqg4knq01q3ek98yyxjrkqmuxn7785aooo2krwl4 4getb7jutdadk

Thanks Bayard and jwickers! Thanks very much for posting these snippets, all these snippets across the various blogs are really very helpful for beginners.

@readers, both the solutions from Bayard and Jwickers works good and both of them provides totally different way of implementing a small handy functionality like this. I got to learn new things with both of them.
For the current purpose I am picking up Jwickers way to implement in my app as it requires lesser code and would be easier on manage.

@_fops
Jwickers has written here in Coffeescript, if you want to get it's JS version you can convert the Coffeescript to Javascript here: http://js2coffee.org/#coffee2js

Thanks very much guys.

  • Sur
over 1 year ago ·
10417
0 iwkw8jyxczfre0cqiuhn8ydqg4knq01q3ek98yyxjrkqmuxn7785aooo2krwl4 4getb7jutdadk

I've also added a clear function in my app, and now we can easily call AppAlert.clear() to clear off all the alerts in one go anywhere from the app.
My final code looks like this (just additional clear function):


    .factory('AppAlert', [
      '$rootScope', function($rootScope) {
        var alertService;
        $rootScope.alerts = [];
        return alertService = {
          add: function(type, msg) {
            return $rootScope.alerts.push({
              type: type,
              msg: msg,
              close: function() {
                return alertService.closeAlert(this);
              }
            });
          },
          closeAlert: function(alert) {
            return this.closeAlertIdx($rootScope.alerts.indexOf(alert));
          },
          closeAlertIdx: function(index) {
            return $rootScope.alerts.splice(index, 1);
          },
          clear: function(){
            $rootScope.alerts = [];
          }
        };
      }
    ])

</code></pre>
over 1 year ago ·
11232
8056864756e89b9f37b083bbbb4b1ce1

Is there a way to use $timeout for closing alerts?

alertService.add = function(type, msg, timeout) {
$rootScope.alerts.push({'type': type, 'msg': msg , close: alertService.closeAlert(this) });
timeout
if (timeout) {
$timeout(function(){
$rootScope.closeAlert($rootScope.alerts.indexOf(alert));
}, timeout);
}
};

over 1 year ago ·
11233
8056864756e89b9f37b083bbbb4b1ce1

Got it!

var alertService = {};
$rootScope.alerts = [];
alertService.add = function(type, msg, timeout) {
$rootScope.alerts.push({
type: type,
msg: msg,
close: function() {
return alertService.closeAlert(this);
}
});
// timeout
if (timeout) {
$timeout(function(){
alertService.closeAlert(this);
}, timeout);
}
};
alertService.closeAlert = function(alert) {
return this.closeAlertIdx($rootScope.alerts.indexOf(alert));
};
alertService.closeAlertIdx = function(index) {
return $rootScope.alerts.splice(index, 1);
};
return alertService;

over 1 year ago ·
11777
03b686421349aa685d73f108176a72ad

This timeout does not work for me. Any further explanation or update would be appreciated. The concept is very nice

over 1 year ago ·
12278
A12b3e5381be4a86b5ce50d5aa780676

This should do it (modifying the above code for readibility):

app.factory('AppAlert', ['$rootScope', '$timeout', function($rootScope, $timeout) { var alertService; $rootScope.alerts = [];

return alertService = {
    add: function(type, msg, timeout) {
        $rootScope.alerts.push({
            type: type,
            msg: msg,
            close: function() {
                return alertService.closeAlert(this);
            }
        });

        if (timeout) { 
            $timeout(function(){ 
                alertService.closeAlert(this); 
            }, timeout); 
        }
    },
    closeAlert: function(alert) {
        return this.closeAlertIdx($rootScope.alerts.indexOf(alert));
    },
    closeAlertIdx: function(index) {
        return $rootScope.alerts.splice(index, 1);
    }

};

}]);
</code>

over 1 year ago ·
14072
Default profile 4 normal

When switching views, it seems to reinitialize this : $rootScope.alerts = [];
Which makes me loose alerts between different pages.

Any solution to this?

over 1 year ago ·
15096
Ab65b7140835a42d94115fa78b2c21ec

Firstly thanks for the tip, I have used this and everything is working nicely with jwickers implementation.

In regards to the timer I found that use case would remove the latest notification first so instead I used:

$timeout(function(){
        alertService.closeAlertIdx(0);
 }, 5000);
over 1 year ago ·
16319
6009dd94b715f7cb1d6a6f7cdd840774

awesome! It's exactly what I need to know!

over 1 year ago ·
16358
6009dd94b715f7cb1d6a6f7cdd840774

This is a version, that simplely inject $sce into the service making the service works while the msg are HTML Tags.

alertService

.factory('alertService', ['$rootScope', '$timeout', '$sce', function ($rootScope, $timeout, $sce) {
var exports;

// create an array of alerts available globally
$rootScope.alerts = [];

function _factory(alertOptions) {
    return $rootScope.alerts.push({
        type: alertOptions.type,
        msg: $sce.trustAsHtml(alertOptions.message),
        close: function () {
            return exports.closeAlert(this);
        }
    });
}

function _addAlert(alertOptions) {
    var index = this.factory(alertOptions) - 1, that = this;
    $timeout(function () {
        that.closeAlertByIndex(index)
    }, 5000);
}

function _closeAlert(alert) {
    return this.closeAlertByIndex($rootScope.alerts.indexOf(alert));
}

function _closeAlertByIndex(index) {
    return $rootScope.alerts.splice(index, 1);
}

exports = {
    factory: _factory,
    addAlert: _addAlert,
    closeAlert: _closeAlert,
    closeAlertByIndex: _closeAlertByIndex
};

return exports;

}]);

View
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="alert.close()"><span ng-bind-html="alert.msg"></span></alert>

over 1 year ago ·
17113
None

@carl-chinatomby , i tried to use your approach but nothing is getting displayed. Currently I am binding my view like this
<alert ng-repeat="alert in alerts" type="alert.type" >
{{ alert.msg }}
</alert>
and controller something like this
$scope.test = function(){
AlertService.add("warning","Test",300);
}

I am new to angular so might be doing something wrong

over 1 year ago ·
17438
None

Great work guys. Love it and incorporated vvian00 version.

over 1 year ago ·
18077
None

Thanks to all for these ideas. I ended combining all of them and making a version that doesn't set anything to $rootScope. Instead I added a .get() method to be retrieved in the directives parent controller. Gist can be viewed here: https://gist.github.com/seyDoggy/66978b761fb8572250f7

over 1 year ago ·
18196
None

vvian00 code has a bug. When alert is spliced out of the array, it makes other index values waiting for timeout event to occur, wrong. This is my version of the code.

.factory('AlertService', ['$rootScope', '$timeout', '$sce', function ($rootScope, $timeout, $sce) {
    var exports;
    // create an array of alerts available globally
    $rootScope.alerts = [];
    var alertId = 0; // unique id for each alert. Starts from 0.
    function _factory(alertOptions)
    {
        return $rootScope.alerts.push({
            type: alertOptions.type,
            msg: $sce.trustAsHtml(alertOptions.msg),
            message: alertOptions.msg, // to view the alerts on the console.
            id : alertOptions.alertId,
            timeoutValue: alertOptions.timeoutValue,
            close: function () {
                return exports.closeAlert(this);
            }
        });
    }
    function _addAlert(alertOptions) {
        alertOptions.alertId = alertId++;
        alertOptions.message = alertOptions.msg;
        var that = this;
        this.factory(alertOptions);
        if (alertOptions.timeoutValue > 0) {
            $timeout(function () {
                that.closeAlert(alertOptions.alertId);
            }, alertOptions.timeoutValue);
        }
    }

    function _closeAlert(id) {
        return this.closeAlertByIndex(_.findIndex($rootScope.alerts, function(eachAlert) { return eachAlert.id === id;}));
    }

    function _closeAlertByIndex(index) {
        console.log('closeAlertByIndex  index=' + index + " msg=" + $rootScope.alerts[index].message);
        return $rootScope.alerts.splice(index, 1);
    }

    exports = {
        factory: _factory,
        addAlert: _addAlert,
        closeAlert: _closeAlert,
        closeAlertByIndex: _closeAlertByIndex
    };

    return exports;
}]);
over 1 year ago ·
21910
None

Please fix it. https://angular-ui.github.io/bootstrap/#/alert - see '{{alert.type}}' in <alert/> ? And there is no alert.type error now, its 'danger'. Your link is first on 'angular bootstrap alert service', I quess many people seeing it

over 1 year ago ·
26622
None

This misses out on the extended functionality but gets around the necessity of polluting the rootscope. Anywhere in the app you can plop down the directive <my-alert-display> to get the current list.

Directive:

angular.module('myApp')
    .directive('myAlertDisplay', ['alertService', function (alertService) {
        return {
            restrict: 'AE',
            template: '<div ng-repeat="alert in vm.alerts" class="alert alert-{{alert.type}}" role="alert"><button ng-click="vm.closeAlert($index)" type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>{{alert.msg}}</div>',
            controller: function(){
                var vm = this;
                vm.alertService = alertService;

                vm.alerts = vm.alertService.alerts;

                vm.closeAlert = function (index) {
                    vm.alertService.closeAlert(index);
                }
            },
            controllerAs: 'vm'
            }
    }]);

Service:

angular.module('myApp')

.factory('alertService', function () {
    var alertService = {};

    // create an array of alerts
    alertService.alerts = [];

    alertService.add = function (type, msg) {
        alertService.alerts.push({ 'type': type, 'msg': msg });
    };

    alertService.closeAlert = function (index) {
        alertService.alerts.splice(index, 1);
    };

    return alertService;
});
over 1 year ago ·