[angularjs] How to unsubscribe to a broadcast event in angularJS. How to remove function registered via $on

I have registered my listener to a $broadcast event using $on function

$scope.$on("onViewUpdated", this.callMe);

and I want to un-register this listener based on a particular business rule. But my problem is that once it is registered I am not able to un-register it.

Is there any method in AngularJS to un-register a particular listener? A method like $on that un-register this event, may be $off. So that based on the business logic i can say

 $scope.$off("onViewUpdated", this.callMe);

and this function stop being called when somebody broadcast "onViewUpdated" event.

Thanks

EDIT: I want to de-register the listener from another function. Not the function where i register it.

This question is related to angularjs

The answer is


EDIT: The correct way to do this is in @LiviuT's answer!

You can always extend Angular's scope to allow you to remove such listeners like so:

//A little hack to add an $off() method to $scopes.
(function () {
  var injector = angular.injector(['ng']),
      rootScope = injector.get('$rootScope');
      rootScope.constructor.prototype.$off = function(eventName, fn) {
        if(this.$$listeners) {
          var eventArr = this.$$listeners[eventName];
          if(eventArr) {
            for(var i = 0; i < eventArr.length; i++) {
              if(eventArr[i] === fn) {
                eventArr.splice(i, 1);
              }
            }
          }
        }
      }
}());

And here's how it would work:

  function myEvent() {
    alert('test');
  }
  $scope.$on('test', myEvent);
  $scope.$broadcast('test');
  $scope.$off('test', myEvent);
  $scope.$broadcast('test');

And here's a plunker of it in action


Looking at most of the replies, they seem overly complicated. Angular has built in mechanisms to unregister.

Use the deregistration function returned by $on :

// Register and get a handle to the listener
var listener = $scope.$on('someMessage', function () {
    $log.log("Message received");
});

// Unregister
$scope.$on('$destroy', function () {
    $log.log("Unregistering listener");
    listener();
});

Register a hook to unsubscribe your listeners when the component is removed:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  

This code works for me:

$rootScope.$$listeners.nameOfYourEvent=[];

@LiviuT's answer is awesome, but seems to leave lots of folks wondering how to re-access the handler's tear-down function from another $scope or function, if you want to destroy it from a place other than where it was created. @?????? ?????????'s answer works just great, but isn't very idiomatic. (And relies on what's supposed to be a private implementation detail, which could change any time.) And from there, it just gets more complicated...

I think the easy answer here is to simply carry a reference to the tear-down function (offCallMeFn in his example) in the handler itself, and then call it based on some condition; perhaps an arg that you include on the event you $broadcast or $emit. Handlers can thus tear down themselves, whenever you want, wherever you want, carrying around the seeds of their own destruction. Like so:

// Creation of our handler:
var tearDownFunc = $rootScope.$on('demo-event', function(event, booleanParam) {
    var selfDestruct = tearDownFunc;
    if (booleanParam === false) {
        console.log('This is the routine handler here. I can do your normal handling-type stuff.')
    }
    if (booleanParam === true) {
        console.log("5... 4... 3... 2... 1...")
        selfDestruct();
    }
});

// These two functions are purely for demonstration
window.trigger = function(booleanArg) {
    $scope.$emit('demo-event', booleanArg);
}
window.check = function() {
    // shows us where Angular is stashing our handlers, while they exist
    console.log($rootScope.$$listeners['demo-event'])
};

// Interactive Demo:

>> trigger(false);
// "This is the routine handler here. I can do your normal handling-type stuff."

>> check();
// [function] (So, there's a handler registered at this point.)  

>> trigger(true);
// "5... 4... 3... 2... 1..."

>> check();
// [null] (No more handler.)

>> trigger(false);
// undefined (He's dead, Jim.)

Two thoughts:

  1. This is a great formula for a run-once handler. Just drop the conditionals and run selfDestruct as soon as it has completed its suicide mission.
  2. I wonder about whether the originating scope will ever be properly destroyed and garbage-collected, given that you're carrying references to closured variables. You'd have to use a million of these to even have it be a memory problem, but I'm curious. If anybody has any insight, please share.

One way is to simply destroy the listener once you are done with it.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })

In case that you need to turn on and off the listener multiple times, you can create a function with boolean parameter

function switchListen(_switch) {
    if (_switch) {
      $scope.$on("onViewUpdated", this.callMe);
    } else {
      $rootScope.$$listeners.onViewUpdated = [];
    }
}

After debugging the code, i created my own function just like "blesh"'s answer. So this is what i did

MyModule = angular.module('FIT', [])
.run(function ($rootScope) {
        // Custom $off function to un-register the listener.
        $rootScope.$off = function (name, listener) {
            var namedListeners = this.$$listeners[name];
            if (namedListeners) {
                // Loop through the array of named listeners and remove them from the array.
                for (var i = 0; i < namedListeners.length; i++) {
                    if (namedListeners[i] === listener) {
                        return namedListeners.splice(i, 1);
                    }
                }
            }
        }
});

so by attaching my function to $rootscope now it is available to all my controllers.

and in my code I am doing

$scope.$off("onViewUpdated", callMe);

Thanks

EDIT: The AngularJS way to do this is in @LiviuT's answer! But if you want to de-register the listener in another scope and at the same time want to stay away from creating local variables to keep references of de-registeration function. This is a possible solution.


'$on' itself returns function for unregister

 var unregister=  $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams, options) { 
                alert('state changing'); 
            });

you can call unregister() function to unregister that listener