1

I have an Angular controller, a fairly simple one:

angular.controller('appCtrl', function ($scope, $rootScope) {
    $rootscope.$on('channel.message', function () {
      // do stuff here
    }
}); 

I have some sidebar on my page, which navigates me to a view attached to controller as above.

The issue is that each time I click on a link, Angular instantiates the controller - that's totally fine, but I can see that the count of subscribers for my 'channel.message' is growing, which is not what I want.

I understand that, well, code just adds another callback to queue, but I'm looking to avoid that issue. I want only a single subscriber. What are best practices here?

BTW: I know about $scope.$on. It doesn't count because of performance implications and architecture design of the app itself.

Eugene P.
  • 2,645
  • 19
  • 23

3 Answers3

3

Maybe try calling your event handler in a named function, which will call it and clean it up. Something like this (also, make sure $rootscope is $rootScope):

angular.controller('appCtrl', function ($scope, $rootScope) {
    var cleanUp = $rootScope.$on('channel.message', function () {
        // do stuff here
    };

    cleanUp();
});

I was having your same issue (with multiple callbacks being added to the queue), and the above solution worked for me.


Also, this is off topic, but I might suggest formatting your controller as follows (just a recommendation):

angular.controller('appCtrl', ['$scope','$rootScope', function($scope, $rootScope) {

}]);
Josh Beam
  • 19,292
  • 3
  • 45
  • 68
  • Thanks, that's really brilliant, I was not aware of it. With regards to code formatting, I'm totally fine with that, I've cook up sample code in a min so...anyway, nice to re-instantiate it in memory :) – Eugene P. May 28 '14 at 20:55
2

As @JoshBeam pointed out the return value of $rootScope.$on() is a function that will deregister your listener. As he pointed out:

var cleanUp = $rootscope.$on('channel.message', function () {
   // do stuff here
}

The remaining question is when to trigger the cleanup. You most likely only want to deregister when the controller is destroyed. Happily just before Angular tears down a scope (due, for instance, to the controller being destroyed) it fires a $destroy event on that particular scope. So you can use the following to trigger a cleanup.

$scope.$on('$destroy', function() {
  cleanUp();
};

Note that, just for tear down purposes, we're listening for a $destroy on $scope (not $rootScope) so we get the message when the controller is torn down. You can leave your regular listener attached to $rootScope.

Also note that this is also a good pattern to use to prevent memory leaks- even if you're not worried about multiple listeners.

KayakDave
  • 24,636
  • 3
  • 65
  • 68
1

I'd recommend to create an $onRootScope method as described in my answer here:

What's the correct way to communicate between controllers in AngularJS?

This automatically abstracts away the deregistration of the event handler and the usage is as simple as $scope.$onRootScope('fooEvent', function(){})

Also please note that the performance implications of broadcasted events became less of an issue with recent versions of angular.

Community
  • 1
  • 1
Christoph
  • 26,519
  • 28
  • 95
  • 133
  • Thanks, it's already more like coding stuff, rather then general idea. but I'm happy to count it. Great link! – Eugene P. May 28 '14 at 21:33