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); }
    });
});

 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

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