Events
In cases where the components of our web application are loosely connected, such as when we require user authentication and handle authorization, it’s not always feasible to handle the immediate communication without coupling our components together.
For example, if our back end responds to a request with a status code of 401 (indicative of an unauthorized request), we expect that our web app won’t allow our user to stay connected to the current view. In this case, we’d want our app to redirect the user to a login or signup page.
Given this logic, we cannot tell our controllers to set a new location from the outside. We also want this specific functionality to space across multiple scopes so we can protect multiple scopes using the same behavior.
We need another way to communicate between them.
Angular’s scopes are hierarchical in nature: They can naturally communicate back and forth through parent-child relationships. Oftentimes, however, our scopes don’t share variables, and they often perform completely different functions from each other, regardless of their place in the parent tree.
For these cases, we have the ability to communicate between our scopes by propagating events up and down the chain.
What are Events
Just as the browser responds to browser-level events, such as a mouse click or a page scroll, our Angular app can respond to Angular events. This fact gives us the advantage of being able to communicate across our application inside nested components that are not built with other components in mind.
Note that the Angular event system does not share the browser event system, meaning that, by design, we can only listen for Angular events, not DOM events on scopes.
We can think of events as snippets of information propagated across an application that generally (optionally) contain information about what’s happening inside of that application.
Event Propagation
Since scopes are hierarchical, we can pass events up or down the scope chain.
A generally good rule of thumb for choosing the event passing method that we’ll use is to look at the scope from which we’re firing the event. If we want to notify the entire event system (thus allowing any scope to handle the event), we’ll want to broadcast downwards.
On the other hand, if we want to alert a global module (so to speak), we’ll end up needing to alert our higher-level scopes ($rootScope
, for instance), and we’ll need to pass an event upwards.
It’s a good idea to limit the number of notifications sent to the global level, particularly because events, although very powerful, introduce complexity into our apps.
For example, when we’re routing, the ‘global’ app state needs to know at which page the app is currently set, while, on the other hand, if we’re communicating between a tab directive to its child pane directives, we’ll need to send the event downwards.
Bubbling an Event Up with $emit
To dispatch an event to travel up the scope chain (from child scopes to parent scopes), we’ll use the $emit()
function.
// Send an event that our user logged in
// with the current user
scope.$emit('user:logged_in', scope.user);
Within an $emit()
event function call, the event bubbles up from the child scope to the parent scope. All of the scopes above the scope that fires the event will receive notification about the event.
We use $emit()
when we want to communicate changes of state from within our app to the rest of the application. If we want to communicate with our $rootScope
then we need to $emit()
the event.
The $emit()
method takes two arguments:
name (string)
The name of the event to emit
args (set)
A set of arguments passed into the event listeners as objects
The $emit()
method returns an event object (see event object for details on the event object).
Any exception that is emitted from any of the listeners passes into the $exceptionHandler
service.
Sending an Event Down with $broadcast
To pass an event downwards (from parent scopes to child scopes), we use the $broadcast()
function.
// hold on, cart is checking out
// so all directives below should disable
// themselves while the cart is checking out
scope.$broadcast('cart:checking_out', scope.cart);
On the $broadcast()
method, every single child scope that registers a listener will receive this message. The event propagates to all directives and indirect scopes of the current scope and calls every single listener all the way down.
We cannot cancel events sent using the $broadcast()
method.
The $broadcast()
method itself takes two parameters:
name (string)
The name of the event to emit
args (set)
A set of arguments passed into the event listeners as objects
The $broadcast()
method returns an event object (see event object for details on the event object).
Any exception that is emitted from any of the listeners passes into the $exceptionHandler
service.
Events
Listening
To listen for an event, we can use the $on()
method. This method registers a listener for the event bearing a particular name. The event name is simply the event type fired in Angular.
For instance, we can listen for the event that fires when a route change process is triggered:
scope.$on('$routeChangeStart',
function(evt, next, current) {
// A new route has been triggered
});
Whenever the event $routeChangeStart
(which is broadcasted when the route is going to be changed) fires, the listener (the function) is called.
Angular passes in the evt
object as the first parameter to any event that we are listening for, be it our own custom events or built-in Angular services.
Event Object
The event
object has the following attributes:
targetScope (scope object)
This attribute is the scope emitting or broadcasting the event.
currentScope (scope object)
This object contains the current scope that is handling the event.
name (string)
This string is the name of the event that was fired and that we are handling.
stopPropagation (function)
The stopPropagation()
function cancels any further event propagation for events that are fired through $emit
.
preventDefault (function)
The preventDefault
function sets the flag of defaultPrevented
to true. Although we cannot stop event propagation, we can tell our child scopes that we don’t need to handle the event (i.e., that we can safely ignore them).
defaultPrevented (boolean)
Calling preventDefault()
sets defaultPrevented
to true.
The $on()
function returns a de-registration function that we can call to cancel the listener.
Core Services Riding on Events
The Angular core framework sends events that we listen for and upon which we can act. We’ll use these events to provide our custom Angular objects the ability to interact with our app at different levels of global state.
There are several events that we call with $emit()
, sending their events upward, and several more that we call as $broadcast()
events.
Core System $emitted Events
The following events are emitted from directives upward to scopes containing the directive invocation. We can use $on()
to listen to these methods in any scope above the chain:
$scope.$on('$includeContentLoaded',
function(evt) {
});
$includeContentLoaded
The $includeContentLoaded
event fires from the ngInclude
directive when the ngInclude
content is reloaded.
$includeContentRequested
The $includeContentRequested
event is emitted on the scope from which the ngInclude
is called. This is emitted every single time that the ngInclude
content is requested.
$viewContentLoaded
The $viewContentLoaded
event is emitted on the current ngView
scope every single time that ngView
content is reloaded.
Core System $broadcasted Events
$locationChangeStart
The $locationChangeStart
event is fired when Angular starts to update the browser’s location based upon a mutation set by the $location
service (through $location.path()
, $location.search()
, etc.).
$locationChangeSuccess
The $locationChangeSuccess
event is broadcasted from the $rootScope
if and only if we have not prevented the $locationChangeStart
event when the location of the browser changes successfully.
$routeChangeStart
The $routeChangeStart
event kicks off from the $rootScope
before a route change occurs. It is at this point that the route services start to resolve all of the dependencies needed for the route change to occur.
This process usually involves fetching view templates and any resolve
dependencies on the route property.
$routeChangeSuccess
The $routeChangeSuccess
event is broadcasted from the $rootScope
after all of the route dependencies are resolved following $routeChangeStart
.
The ngView
directive uses the $routeChangeSuccess
event to know when to instantiate the controller and render the view.
$routeChangeError
The $routeChangeError
event is fired if any of the resolve
properties on the route object are rejected (i.e., if they fail). This event is broadcasted from the $rootScope
.
$routeUpdate
The $routeUpdate
is broadcasted from the $rootScope
if the reloadOnSearch
property on the $routeProvider
has been set to false and the same instance of a controller is being used.
$destroy
The $destroy
event is broadcasted on the scope before the scope is destroyed. This sequence gives the children scopes a chance to clean themselves up before the parent scope is actually removed.
For instance, if we have a $timeout
running in our controller, we don’t want this event to continuously fire even if the containing controller no longer exists.
angular.module('myApp')
.controller('MainController', function($scope, $timeout) {
var timer;
var updateTime = function() {
$scope.date = new Date();
timer = $timeout(updateTime, 1000);
}
// Start updating time
timer = $timeout(updateTime, 1000);
// Clean up the timer before we kill this
// controller
$scope.$on('$destroy', function() {
if (timer) { $timeout.cancel(timer); }
});
});
Ready to master AngularJS?
- What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
- Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
- You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now