Search code examples
angularjsangularjs-directiveangularjs-scopeangularjs-service

What is best practice/alternative to use $broadcast/$on in triggering events in angularjs?


I have scenario where I want to communicate between sibling controllers in different apps. So I created sample demo which uses publisher-subscriber service to broadcast and listen event. But the code subscribing to the event is in the controller. So I want to understand whether this is a best practice? What is the alternative way to achieve the same, give example?

I indicated following scenario –
controllerA broadcast event and controllerB and controllerC listen to it (1-Many)

var app = angular.module('app', []);

app.controller('controllerA', ['$scope', 'pubsubService', controllerA]);

function controllerA($scope, pubsubService) {
  $scope.teamName = '';
  $scope.changeTeam = function() {
    pubsubService.Publish("changeNameEvent", {
      filterTeam: $scope.teamName
    });
  };
}

app.controller('controllerB', ['$scope', 'pubsubService', controllerB]);

function controllerB($scope, pubsubService) {
  var callbackNameChanged = function(message) {
    $scope.team = message.filterTeam

  };
  pubsubService.Subscribe("changeNameEvent", $scope, callbackNameChanged);
}

app.controller('controllerC', ['$scope', 'pubsubService', controllerC]);

function controllerC($scope, pubsubService) {
  var callbackNameChanged = function(message) {
    $scope.team = message.filterTeam
  };
  pubsubService.Subscribe("changeNameEvent", $scope, callbackNameChanged);
}

app.factory("pubsubService", ["$rootScope", function($rootScope) {
  var Publish = function(message, item) {
    try {
      $rootScope.$broadcast(message, {
        item: item
      })
    } catch (e) {
      console.log(e.message)
    }
  };
  var Subscribe = function(message, $scope, handler) {
    try {
      $scope.$on(message, function(event, args) {
        handler(args.item)
      })
    } catch (e) {
      console.log(e.message)
    }
  };
  return {
    Publish: Publish,
    Subscribe: Subscribe
  }
}]);

Html Code:

<body class='container'>
  <div ng-controller="controllerA">
    <input data-ng-model="teamName" type="text" data-ng-change="changeTeam()" />    
  </div>
  <div ng-controller="controllerB">controllerB - You typed: {{team}}
    <br />
  </div>
  <div ng-controller="controllerC">controllerC - You typed:{{team}}</div>
</body>

Solution

  • After the analysis I come up with following solution to move subscription logic to a directive with "&" operator parameter that allows to invoke or evaluate an expression/function on the parent scope and keep controller code to minimum. As dumping things onto the controller is a bad idea 99% of the time. Unless it's a scope variable or a watch you can most likely abstract it into something else.

    By implementing this way we can make code reusable, testable and modular.

    app.directive('onChangeName', ['pubsubService', function(pubsubService) {
      return {
        restrict: 'EA',
        scope: {
          onNameChangeCallback: '&'
        },
        link: function(scope, element) {
          pubsubService.Subscribe("changeNameEvent", scope, function(message) {
            scope.onNameChangeCallback({
              message: message.filterTeam
            });
          });
        }
      };
    }]);
    
    app.controller('controllerB', function($scope){
      $scope.callbackNameChanged = function(message) {
        $scope.team = message
      };
    });
    
    app.controller('controllerC', function($scope){
      $scope.callbackNameChanged = function(message) {
         $scope.team = message
      };
    });
    

    Html Code

    <div ng-controller="controllerB">
        <on-change-name on-name-change-callback="callbackNameChanged(message)"></on-change-name>
        controllerB - You typed: {{team}}
        <br />
    </div>
    <div ng-controller="controllerC">
        <on-change-name on-name-change-callback="callbackNameChanged(message)"></on-change-name>
        controllerC - You typed:{{team}}
    </div>