Last Updated: March 21, 2023
·
85.83K
· 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.

22 Responses
Add your response

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 ·

Thanks, this helped me a lot!

over 1 year ago ·

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 ·

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 ·

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 ·

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 ·

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 ·

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 ·

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 ·

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

over 1 year ago ·

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 ·

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 ·

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 ·

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

over 1 year ago ·

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 ·

@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 ·

Great work guys. Love it and incorporated vvian00 version.

over 1 year ago ·

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 ·

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 ·

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 ·

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 ·

ths

'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;
  });
over 1 year ago ·