Book Description
Packed with easy-to-follow recipes, this practical guide will show you how to unleash the full might of the AngularJS framework. Skip straight to practical solutions and quick, functional answers to your problems without hand-holding or slogging through the basics. Avoid antipatterns and pitfalls, and squeeze the maximum amount out of the most powerful parts of the framework, from creating promise-driven applications to building an extensible event bus. Throughout, take advantage of a clear problem-solving approach that offers code samples and explanations of components you should be using in your production applications.
Read an Extract from the book
Configuring and using AngularJS events
AngularJS offers a powerful event infrastructure that affords you the ability to control the application in scenarios where data binding might not be suitable or pragmatic. Even with a rigorously organized application topology, there are lots of applications for events in AngularJS.
How to do it…
AngularJS events are identified by strings and carry with them a payload that can take the form of an object, a function, or a primitive. The event can either be delivered via a parent scope that invokes $scope.$broadcast(), or a child scope (or the same scope) that invokes $scope.$emit().
The $scope.$on() method can be used anywhere a scope object can be used, as shown here:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope, $log) { $scope.$on('myEvent', function(event, data) { $log.log(event.name + ' observed with payload ', data); }); });
Broadcasting an event
The $scope.$broadcast() method triggers the event in itself and all child scopes. The 1.2.7 release of AngularJS introduced an optimization for $scope.$broadcast(), but since this action will still bubble down through the scope hierarchy to reach the listening child scopes, it is possible to introduce performance problems if this is overused. Broadcasting can be implemented as follows:
(app.js) angular.module('myApp', []) .directive('myListener', function($log) { return { restrict: 'E', // each directive should be given its own scope scope: true, link: function(scope, el, attrs) { // method to generate event scope.sendDown = function() { scope.$broadcast('myEvent', {origin: attrs.local}); }; // method to listen for event scope.$on('myEvent', function(event, data) { $log.log( event.name + ' observed in ' + attrs.local + ', originated from ' + data.origin ); }); } }; }); (index.html) <div ng-app="myApp"> <my-listener local="outer"> <button ng-click="sendDown()">Send Down</button> <my-listener local="middle"> <my-listener local="first inner"></my-listener> <my-listener local="second inner"></my-listener> </my-listener> </my-listener> </div>
In this setup, clicking on the Send Down button will log the following in the browser console:
myEvent observed in outer, originated from outer myEvent observed in middle, originated from outer myEvent observed in first inner, originated from outer myEvent observed in second inner, originated from outer
Emitting an event
As you might expect, $scope.$emit() does the opposite of $scope.$broadcast(). It will trigger all listeners of the event that exist within that same scope, or any of the parent scopes along the prototype chain, all the way up to $rootScope. This can be implemented as follows:
(app.js) angular.module('myApp', []) .directive('myListener', function($log) { return { restrict: 'E', // each directive should be given its own scope scope: true, link: function(scope, el, attrs) { // method to generate event scope.sendUp = function() { scope.$emit('myEvent', {origin: attrs.local}); }; // method to listen for event scope.$on('myEvent', function(event, data) { $log.log( event.name + ' observed in ' + attrs.local + ', originated from ' + data.origin ); }); } }; }); (index.html) <div ng-app="myApp"> <my-listener local="outer"> <my-listener local="middle"> <my-listener local="first inner"> <button ng-click="sendUp()"> Send First Up </button> </my-listener> <my-listener local="second inner"> <button ng-click="sendUp()"> Send Second Up </button> </my-listener> </my-listener> </my-listener> </div>
In this example, clicking on the Send First Up button will log the following to the browser console:
myEvent observed in first inner, originated from first inner myEvent observed in middle, originated from first inner myEvent observed in outer, originated from first inner
Clicking on the Send Second Up button will log the following to the browser console:
myEvent observed in second inner, originated from second inner myEvent observed in middle, originated from second inner myEvent observed in outer, originated from second inner
Deregistering an event listener
Similar to $scope.$watch(), once an event listener is created, it will last the lifetime of the scope object they are added in. The $scope.$on() method returns the deregistration function, which must be captured upon declaration. Invoking this deregistration function will prevent the scope from evaluating the callback function for this event. This can be toggled with a setup/teardown pattern, as follows:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope, $log) { $scope.setup = function() { $scope.teardown = $scope.$on('myEvent',function(event, data) { $log.log(event.name + ' observed with payload ', data); }); }; });
Invoking $scope.setup() will initialize the event binding, and invoking $scope.teardown() will destroy that binding.