Search code examples
javascriptangularjseventsangularjs-routing

Can't stop event propagation between route controllers in AngularJS


I am using Angular Route to build an SPA with routes encapsulated by a common, parent controller. Inside the parent controller, there is a button that will emit an event that is suppose to be caught by all child/route controllers, each decide what to do with it.

And I would like for this event to be emitted once by the parent controller, and received once by each of the child/route controllers. I have a Plunker here http://plnkr.co/edit/9twoe6WO3eLQzVDatrC0?p=preview that shows an unsuccessful attempt.

The issue with the snippet is that every time a route navigation occurs, the event is received one more time by the same child/route controller.

PS: code from Plunker

HTML

  <body ng-controller="MainCtrl">
    <ng-view></ng-view>
    <button ng-click="navigate()">Propagate</button>
    <script type="text/ng-template" id="viewIndexCtrl.html">
      <p>this is index page</p>
    </script>
    <script type="text/ng-template" id="viewPackageCtrl.html">
      <p>this is package page</p>
    </script>
  </body>

JS

var app = angular.module('plunker', ['ngRoute']);
app.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.when('/index', {
      controller: 'viewIndexCtrl',
      templateUrl: 'viewIndexCtrl.html'
    }).when('/package', {
      controller: 'viewPackageCtrl',
      templateUrl: 'viewPackageCtrl.html'
    }).otherwise({
      redirectTo: '/index'
    });
  }
]);

app.controller('MainCtrl', function($scope, $location, $rootScope) {
  $scope.navigate = function() {
    var route = location.href.indexOf('index') > -1 ? 'package' : 'index';

    $location.path('/' + route);
    $rootScope.$emit('navigate');
  };
});

app.controller('viewIndexCtrl', function($scope, $rootScope) {
  $rootScope.$on('navigate', function(e) {
    e.preventDefault();
    e.stopPropagation();

    console.log('event reached index');
  });
});
app.controller('viewPackageCtrl', function($scope, $rootScope) {
  $rootScope.$on('navigate', function(e) {
    e.preventDefault();
    e.stopPropagation();

    console.log('event reached package');
  });
});

Solution

    • What you need is to use $broadcast rather than $emit.
    • you put listeners on $rootScope, whenever a controller is destroyed it's scope is destroyed too thus all it's scope listeners are removed automatically, but when you put the listeners on $rootScope you need to manually remove them. what happens in your code is that you attach one listener to $rootscope with each route change without removing the previous listeners. If you'll use the controller's local $scope you don't have to do anything.

    This is probably what you need (plunker):

    app.controller('MainCtrl', function($scope, $location, $rootScope, $timeout) {
      $scope.navigate = function() {
        var route = location.href.indexOf('index') > -1 ? 'package' : 'index';
        $location.path('/' + route);
        // $location.path happens only after the digest cycle so we must wait for it..
        $timeout(function() {
          $rootScope.$broadcast('navigate');  
        });
      };
    });
    
    app.controller('viewIndexCtrl', function($scope, $rootScope) {
      $scope.$on('navigate', function(e) {
        console.log('event reached index');
      });
    });
    app.controller('viewPackageCtrl', function($scope, $rootScope) {
      $scope.$on('navigate', function(e) {
        console.log('event reached package');
      });
    });