Publish-subscribe Pattern

While building Hack Reactor: The Game, we often run into the situation where Angular controllers need to communicate with each other. For example, when a game starts, it generates a set of puzzles and the manual required to solve them.

Hack Reactor Game Manual

Since the game screen and the manual are located in different Angular views, we need a way for the game controller to tell the manual controller that the manual is ready to be loaded. A good way to accomplish this is using the Publish-subscribe (Pub/Sub) pattern.

Implementation

To keep things organized, we've put all the Pub/Sub functions in an Angular service factory:

angular.module('app.comm', [])

.factory('requestNotification', ['$rootScope',
  function($rootScope) {
    // private notification messages
    var _LOAD_MANUAL_ = '_LOAD_MANUAL_';

    // publish load manual notification
    var loadManual = function(manual) {
      $rootScope.$broadcast(_LOAD_MANUAL_, manual);
    };

    // subscribe to load manual notification
    var onLoadManual = function($scope, handler) {
      $scope.$on(_LOAD_MANUAL_, function(event, manual) {
        handler(manual);
      });
    };

    return {
      loadManual: loadManual,
      onLoadManual: onLoadManual
    };
  }
]);

When our game controller generates the manual, it will call the loadManual function, which publishes/broadcasts the _LOAD_MANUAL_ event along with the manual content in $rootScope for any controller that is actively listening for the event:

angular.module('app.game', [])

.controller('GameController', [$scope', 'requestNotification',
  'Puzzle',
  function($scope, requestNotification, Puzzle) {
    // Puzzle service generates a set of puzzles
    // with the associated manual
    var puzzles = Puzzle.generatePuzzles();

    // publish the loadManual event
    requestNotification.loadManual(puzzles.manual);
  }
]);

Any controller that needs to listen for the event has to subscribe to it by calling the onLoadManual function. When the event is triggered, the specified code in the Pub/Sub service will run. In this case, the onLoadManualhandler callback function will be called with the manual content passed in:

angular.module('app.manual', [])

.controller('ManualController', [$scope', 'requestNotification',
  function($scope, requestNotification) {
    var onLoadManualHandler = function(manual) {
      // store the manual in the scope of this controller
      $scope.manual = manual;
    };

    // subscribe to loadManual event
    requestNotification.onLoadManual($scope, onLoadManualHandler);
  }
]);

With the manual content loaded in ManualController, the manual content can now be properly displayed in the Angular view.

Written on October 24, 2015