AngularJS Event Notification System - $broadcast, $emit and $on functions

Waqas Anwar
27 February 2016
3090 Views

One of the important aspects of AngularJS single page apps is the ability to send event notifications to different areas of the app. You may have two different areas of the page which needs to communicate with each other based on some user action or some data change. Some of the controllers might be waiting for some event to occur such as waiting for particular data to be loaded from the server using AJAX request. In scenarios like these you can use AngularJS event dispatching and handling methods to notify all the interested listeners of your application so that they can respond to those events accordingly.

To fully understand how the event system works in AngularJS, you need to have a basic understanding of scopes and their hierarchy in AngularJS. In an AngularJS application, the controllers and views share an object called $scope. The controller sets properties on the scope and the view binds to those properties. The $scope objects used by your controllers or views are organized into a hierarchy. On top of the hierarchy, there is a $rootScope which have one or more child scopes defined in a hierarchy. Every controller you creates in AngularJS becomes the direct or indirect child of $rootScope. 

Propagation of events in AngularJS App

$scope.$emit(name,args)

The event notification can be dispatched from child controllers to parent controllers upwards in the $scope hierarchy using $emit function available through $scope object. During this process all the registered event listeners get notified and called until the event life cycle reach the $rootScope. The event will stop propagating if one of the event listeners in the hierarchy cancels it.

The first parameter to the $emit() function is the name of the event that is being emitted. The scopes upwards in the hierarchy should register event listeners for this event name using the $on function. The second parameter args can be one or more parameters you want to share with listeners.

$scope.$broadcast(name, args)

The event notification can be send from parent controllers to all child controllers downwards in the $scope hierarchy using $broadcast function available through $scope object. Once the event is broadcasted it cannot be cancelled by any registered listeners.

The first parameter to the $broadcast() function is the name of the event that is being broadcasted. The scopes downwards in the hierarchy should register event listeners for this event name using the $on function. The second parameter args can be one or more parameters you want to share with listeners.

$scope.$on(name, eventListenerFunction)

The $on function is used to register event listeners which can be called when the different events occur in the AngularJS app. The first parameter of the $on function is the name of the event you are interested in listening. The second parameter is an event listener function which gets called when the event occurs. The listener function takes two parameters event and args. The event object has several useful properties such as name, targetScope and currentScope that give more information about the event. The event object also has a function stopPropagation which can be used to stop further propagation of the event. The args parameter gives access to the data that is passed from the emitted/broadcasted events.

Real World Example

It is time now to demonstrate the usage of AngularJS event notification system by building a real world example of a banking application. The app allows a user to either deposit or withdraws a certain amount in or out of the bank account and at the same time displays the current account balance on the header of the page. The app consists three controllers: AccountControllers, DepositController and WithdrawController. The DepositController and WithdrawController are nested in AccountController in the HTML so AccountController scope is the parent of both of these child controllers.



Propagation of events in AngularJS App
Every time user enter some value in Deposit Controller text field and click Deposit button the DepositController dispatches AmountDeposited event upwards to AccountController using the $scope.$emit function. Similarly, every time user enter some value in Withdraw Controller text field and click Withdraw button the WithdrawController dispatches AmountWithdrawn event upwards to AccountController using the $scope.$emit function. The AccountController has registered event listeners for both of these events so respective listeners gets called and update the account balance accordingly. If user try to withdraw any amount greater than the current balance then AccountController dispatches WithdrawalNotAllowed event downwards to WithdrawController using $scope.$broadcast function. The WithdrawController has registered event listener for this event which gets called and display error message to the user. 

Propagation of events in AngularJS App
The following is the complete code of app.js file in which all these controllers are defined:

app.js

(function () {
   'use strict';
 
   angular
      .module('app', [])
      .controller('AccountController', AccountController)
      .controller('DepositController', DepositController)
      .controller('WithdrawController', WithdrawController);

      AccountController.$inject = ['$scope']; 
      function AccountController($scope) {
         var vm = this;
         vm.accountBalance = 0;
         vm.activate = _activate;

         function _activate()
         {
            $scope.$on("AmountDeposited", _amountDepositedHandler);
            $scope.$on("AmountWithdrawn", _amountWithdrawnHandler);
         }

         function _amountDepositedHandler(event, args)
         {            
            vm.accountBalance += eval(args.amount);
         }
 
         function _amountWithdrawnHandler(event, args)        
         {
            if(vm.accountBalance - eval(args.amount) < 0)
            {
               $scope.$broadcast("WithdrawalNotAllowed", { balance: vm.accountBalance });
            }
            else
            {
               vm.accountBalance -= eval(args.amount);
            }
         }

         _activate();
      }

      DepositController.$inject = ['$scope'];
      function DepositController($scope) {
         var vm = this;
         vm.amount = 0;
         vm.deposit = _deposit;

         function _deposit()
         {
            $scope.$emit("AmountDeposited", {amount: vm.amount});
            vm.amount = 0;
         }
      }

      WithdrawController.$inject = ['$scope'];
      function WithdrawController($scope) {
         var vm = this; 
         vm.amount = 0;
         vm.validationError = "";
         vm.activate = _activate;
         vm.withdraw = _withdraw;

         function _activate()
         {
            $scope.$on("WithdrawalNotAllowed", _withdrawalNotAllowedHandler);
         }

         function _withdraw()
         {
            vm.validationError = "";
            $scope.$emit("AmountWithdrawn", {amount: vm.amount});
            vm.amount = 0;
        }
        function _withdrawalNotAllowedHandler(event, args)
        {
           vm.validationError = "You cannot withdraw more than $" + args.balance;
        }
       _activate();
   }

})();


The following code shows the usage of these controllers in index.html file 

index.html

<!DOCTYPE html>
<html>
<head>
   <link  rel="stylesheet" href="css/bootstrap.min.css" />
   <link  rel="stylesheet" href="css/custom.css" />
</head>
<body ng-app="app">
   <div class="container" ng-controller="AccountController as vm">
      <div class="header clearfix">
           <nav>
               <ul class="nav nav-pills pull-right">
                   <li role="presentation">
                       <span>Current Balance: {{ vm.accountBalance | currency }}</span>
                   </li>
               </ul>
           </nav>
           <h3 class="text-muted">Account Controller</h3>
           <h5>dispatches event <b>WithdrawalNotAllowed</b> downwards to Child Controllers using <b>$broadcast</b></h5>
      </div>
      <div class="row">
         <div class="col-lg-6" ng-controller="DepositController as vm">
            <h3>Deposit Controller</h3>
            <h5>dispatches event <b>AmountDeposited</b> upwards to AccountController using <b>$emit</b></h5>
            <p>
               <input type="text" class="form-control" ng-model="vm.amount" />
            </p>
            <p>
               <input type="button" class="btn btn-primary btn-sm" value="Deposit" ng-click="vm.deposit()" />
            </p>
        </div>
        <div class="col-lg-6" ng-controller="WithdrawController as vm">
           <h3>Withdraw Controller</h3>
           <h5>dispatches event <b>AmountWithdrawn</b> upwards to AccountController using <b>$emit</b></h5>
           <p>
              <input type="text"  class="form-control" ng-model="vm.amount" />
              <span class="error" ng-if="vm.validationError">{{vm.validationError}}</span>
           </p>
           <p>
              <input type="button" class="btn btn-primary btn-sm" value="Withdraw" ng-click="vm.withdraw()" />
           </p>
        </div>
      </div>
   </div>

   <script type="text/javascript" src="js/jquery.min.js"></script>
   <script type="text/javascript" src="js/bootstrap.min.js"></script>
   <script type="text/javascript" src="js/angular.min.js"></script>
   <script type="text/javascript" src="js/custom.js"></script>
   <script type="text/javascript" src="app/app.js"></script>
</body>
</html>


As you can see, it is very easy to send notifications and share data between parent and child controllers using the broadcast and emit functions. Sometimes your app is structured in such a way where there is no parent and child relation between controllers and still you want to send notifications and share data between two different controllers. In those situations, you can send event notifications using $rootScope.broadcast function. $rootScope is the parent of all the scopes in AngularJS application so if you will broadcast event from the $rootScope it will propagate to all the child controllers and any child controller can register listener to listen to that event.

I hope you now have better understanding how the event notification system works in AngularJS. If you have difficulties in implementing the code or if you want to play around with the code example then you can download the complete source code.