67

I have the following:

angular.module('test')
    .controller('QuestionsStatusController1',
    ['$rootScope', '$scope', '$resource', '$state',
    function ($rootScope, $scope, $resource, $state) {

        $scope.action2 = function() {
            $rootScope.$broadcast('action2@QuestionStatusController1');
        }

    }]);

angular.module('test')
   .controller('QuestionsStatusController2',
   ['$rootScope', '$scope', '$resource', '$state',
   function ($rootScope, $scope, $resource, $state) {

    $rootScope.$on('action2@QuestionStatusController1', function {
         //write your listener here
    })

   }]);

It's my understanding that I need to unregister the listening event. Can someone tell me how I could code / do this?

  • 1
    possible duplicate of [How to unsubscribe to a broadcast event in angularJS. How to remove function registered via $on](http://stackoverflow.com/questions/14898296/how-to-unsubscribe-to-a-broadcast-event-in-angularjs-how-to-remove-function-reg) – zs2020 Sep 17 '13 at 21:15
  • 2
    When you broadcast from the $rootScope, the whole point is that you don't need to listen from the $rootScope anymore -- it propagates to all scopes in your system. My advice is -- use $scope.$on so that if your scope gets destroyed or replaced by route change, it will automatically de-register it's own listeners. Otherwise, you will have lingering closures that execute even if the listening scopes that use the event are long gone – neaumusic Dec 18 '14 at 21:06
  • @neaumusic, Great.. ! Your tip helped me.. :) – Suresh PMS Nov 24 '16 at 10:29

4 Answers4

151

If you don't un-register the event, you will get a memory leak, as the function you pass to $on will not get cleaned up (as a reference to it still exists). More importantly, any variables that function references in its scope will also be leaked. This will cause your function to get called multiple times if your controller gets created/destroyed multiple times in an application. Fortunately, AngularJS provides a couple of useful methods to avoid memory leaks and unwanted behavior:

  • The $on method returns a function which can be called to un-register the event listener. You will want to save your de-register function as a variable for later use: var cleanUpFunc = $scope.$on('yourevent', ...); See the documentation for $on: http://docs.angularjs.org/api/ng.$rootScope.Scope#$on

  • Whenever a scope gets cleaned up in Angular (i.e. a controller gets destroyed) a $destroy event is fired on that scope. You can register to $scope's $destroy event and call your cleanUpFunc from that.

You can tie these two helpful things together to clean up your subscriptions properly. I put together an example of this: http://plnkr.co/edit/HGK9W0VJGip6fhYQQBCg?p=preview. If you comment out the line cleanUpFunc(); and then hit the toggle and do stuff button a few times, you will notice that our event handler gets called multiple times, which is not really desired.

Now, after all of that, to make your specific situation behave correctly, just change your code in QuestionsStatusController2 to the following:

angular.module('test')
   .controller('QuestionsStatusController2',
   ['$rootScope', '$scope', '$resource', '$state',
   function ($rootScope, $scope, $resource, $state) {

    var cleanUpFunc = $rootScope.$on('action2@QuestionStatusController1', function {
         //write your listener here
    });

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

   }]);

By calling cleanUpFunc() in $destroy, your event listener for the action2@QuestionStatusController1 event will be un-subscribed and you will no longer be leaking memory when your controller gets cleaned up.

Catalin MUNTEANU
  • 5,618
  • 2
  • 35
  • 43
DigitalZebra
  • 39,494
  • 39
  • 114
  • 146
43

Register the listener on the local $scope, not the $rootScope, and the listener will be destroyed automatically when the controller is removed.

So to publish

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);

And subscribe

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);

Plunker

Community
  • 1
  • 1
poshest
  • 4,157
  • 2
  • 26
  • 37
  • 1
    I'd like to emphasize that this answer actually applies to most cases including the OP's where we want to deregister a listener. Angular will automatically do this for us when the scope is destroyed if we attach the listener to the local scope. This also means that the event will need to be `$broadcast-ed` versus `$emit-ed` so that it reaches the scope but that seems to be more intuitive to me as well. – ragamufin Feb 09 '15 at 06:32
15

Here is the source code about the deregistration logic. You can do:

$rootScope.$on('action2@QuestionStatusController1', function () {
    $rootScope.$$listeners['action2@QuestionStatusController1'] = [];
})

or call the deregistration function returned from $on()

var deregistration = $rootScope.$on('action2@QuestionStatusController1', function () {
    deregistration();
})
zs2020
  • 53,766
  • 29
  • 154
  • 219
0
$scope.$on('saveCancelLeadInfo', function (event, args) {

        if ($scope.$$listenerCount["saveCancelLeadInfo"] > 1) {

            $scope.$$listenerCount["saveCancelLeadInfo"] = 0;

        } });
Viral Patel
  • 32,418
  • 18
  • 82
  • 110
  • Although this code may answer the question, providing additional context regarding _why_ and/or _how_ it answers the question would significantly improve its long-term value. Please [edit] your answer to add some explanation. – Toby Speight May 30 '16 at 13:00
  • 1
    `$$` prefixed properties should be avoided – danwellman Mar 17 '17 at 09:28