AngularJS: Communicating between Scopes

One of the many features of the AngularJS JavaScript framework is scope.  Scope is a way to encapsulate data relevant to a particular piece of functionality and keep it separate from functionality not directly related to it.  While this separation is desirable, from it rises the need to communication and pass data between scopes.

From what I can tell, there are six ways to communicate between scopes in AngularJS:

  1. Global Variables
  2. Parent/Child direct communication
  3. Cookies
  4. Shared Properties Services
  5. Binding
  6. Events

Global Variables

Using global variables is a very simple way to pass data between scopes.  Use of global variables is generally perceived to be a bad idea, however, because these variables can easily, and accidentally, be overwritten.  Additionally, this practice leads to spaghetti code.

Parent/Child or Sibling Communication

Direct communication between parent and child scopes or sibling scopes is also a possibility but this is usually a bad idea as well.  For one, communication in this manner causes tight coupling.  Additionally, the mechanism for communication between scopes in this manner is clunky.  Child scopes are implemented as linked lists.  This means that each parent has a link to the first and last child scope, and those child scopes have a link to their next sibling and previous sibling.  While there could be certain situations where this kind of communication is desirable, generally there is a better way.

Cookies

Use of cookies works well but should generally be used for their intended purpose which is to persist data locally.  If cookies are abused, you run into the same problem as global variables.  Also, cookies only store string data so attempting to communicate any non-string data would have the overhead of serialization.

Shared Properties Services

Shared properties services are a good way to share and cache data between scopes.  These are objects that are injected into controllers and directives using mechanisms provided by AngularJS which are intended to contain data common to each.  While communication can be achieved in this manner using watchers on a specified shared property, generally this is most useful for caching purposes.

To define a shared properties serivce:


angular.module('Derby.services').service('sharedProperties', [function () {
    return {
        selectedDivisionId: 0,
        //...Define other default shared properties here
    };
}]);

To use a shared properties service, inject it into a controller or directive and use it as an object:


angular.module('RaceTrackerApp.heats', []).controller('HeatsController', ['$scope', 'sharedProperties',
    function ($scope, sharedProperties) {
        //You must add sharedProperties to $scope in order to watch it
        $scope.sharedProperties = sharedProperties;

        //Watch the selectedDivisionId for changes
        $scope.$watch('sharedProperties.selectedDivisionId', 
            //This function will be executed when selectedDivisionId changes 
            //from anywhere
            function(newValue,oldValue){
                //Do something
            }
        );

Binding

Binding in AngularJS is a built-in way to easily pass data between parent and child scopes through HTML.  To facilitate communication, you can add a watcher on a bound variable.  When this variable changes, the watcher will fire allowing you to take some additional action.  The downside to binding is that it is difficult to achieve outside of HTML and therefore if the two scopes in which you want to communicate are not connected via HTML then binding should probably not be used.

To use binding, define a variable in your parent scope as such:


angular.module('RaceTrackerApp.heats', []).controller('HeatsController', ['$scope',
    function ($scope) {
        $scope.heats = [];

And use the variable in HTML as such:


<div ng-repeat="heat in heats">

Events

The final method of communicating between scopes is using events.  There are two kinds of events in AngularJS:

  • Broadcast – Event bubbles downward to all decentant scopes
  • Emit – Events bubble upwards to all ancestor scopes

Besides the fact that each event type bubbles in a different direction, these both work the same.  Each can pass in additional payload data.  Also, each can be watched for using $on.

To dispatch an event:


//heatCardClicked is the event name and heatDriverYear is the payload
$scope.$emit('heatCardClicked', heatDriverYear);

$broadcast works in the same way but bubbles in a different direction. To watch for an event:


//Watches for the heatCardClicked event.  Expects a heatDriverYear payload
$scope.$on('heatCardClicked', 
    function(event, heatDriverYear){
        //Do stuff after the heat card is clicked
    }
);