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.
Written by Bayard Randel
Related protips
22 Responses
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()"
Thanks, this helped me a lot!
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.
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
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
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
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>
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);
}
};
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;
This timeout does not work for me. Any further explanation or update would be appreciated. The concept is very nice
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>
When switching views, it seems to reinitialize this : $rootScope.alerts = [];
Which makes me loose alerts between different pages.
Any solution to this?
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);
awesome! It's exactly what I need to know!
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>
@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
Great work guys. Love it and incorporated vvian00 version.
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
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;
}]);
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
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">×</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;
});
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;
});