Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: James Allen and Matt Gifford
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: James Allen@CFJamesAllen ) and Matt Gifford@coldfumonkeh )

Unbinding Scope.$on() Event Handlers In AngularJS

By Ben Nadel on

Most of the time, we don't have to think about unbinding Scope.$on() event handlers in AngularJS because they are implicitly unbound when the current scope is destroyed. But, if you think about the scope tree as a built-in pub/sub (Publish and Subscribe) mechanism, you can imagine scenarios in which it would be useful to explicitly unbind an event handler. To do so, we use the same approach that we needed in order to unbind Scope.$watch() event handlers - we store a reference to the deregistration function.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

When you call the Scope.$on() method, in AngularJS, the return value is a deregistration function for the bound event handler. When you invoke that deregistration function, it's basically like calling .off() on that event handler - it unbinds it (even though there is no .off() method).

To see this in action, I have two instances of a Controller that are listening for a "ping" event that is being continuously $broadcast() from the parent $scope. When you click on either of the relevant Views, the Controller will toggle the event binding and stop updating the view-model:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Unbinding Scope.$on() Event Handlers In AngularJS
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Unbinding Scope.$on() Event Handlers In AngularJS
  • </h1>
  •  
  • <div
  • ng-controller="EventController"
  • ng-click="toggleListener()"
  • class="event-target left"
  • ng-class="{ active: isWatchingEvent }">
  •  
  • {{ eventCount }}
  •  
  • </div>
  •  
  • <div
  • ng-controller="EventController"
  • ng-click="toggleListener()"
  • class="event-target right"
  • ng-class="{ active: isWatchingEvent }">
  •  
  • {{ eventCount }}
  •  
  • </div>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.2.26.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope, $interval ) {
  •  
  • // Continuously broadcast an event down the scope tree.
  • $interval(
  • function handleInterval() {
  •  
  • $scope.$broadcast( "ping" );
  •  
  • },
  • 200
  • );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the event targets.
  • app.controller(
  • "EventController",
  • function( $scope ) {
  •  
  • // I keep track of the number of times we've handled a given event.
  • $scope.eventCount = 0;
  •  
  • // I keep track of whether or not this controller is currently listening
  • // for the broadcast event.
  • $scope.isWatchingEvent = false;
  •  
  • // I am the deregistration method for the event handler. This is the
  • // closest thing we have to a scope.$off() method.
  • var unbindHandler = null;
  •  
  • // When the controller loads, start listening for broadcast events.
  • startWatchingEvent();
  •  
  •  
  • // ---
  • // PUBLIC METHODS
  • // ---
  •  
  •  
  • // I turn on / off event listening depending on the current state.
  • $scope.toggleListener = function() {
  •  
  • unbindHandler
  • ? stopWatchingEvent()
  • : startWatchingEvent()
  • ;
  •  
  • };
  •  
  •  
  • // ---
  • // PRIVATE METHODS
  • // ---
  •  
  •  
  • // I respond to the "ping" event on the scope.
  • function handlePingEvent( event ) {
  •  
  • $scope.eventCount++;
  •  
  • }
  •  
  •  
  • // I start watching for the "ping" event on the scope.
  • function startWatchingEvent() {
  •  
  • // When we bind the $on() event, the return value is the
  • // deregistration method for the event handler. This is the way we
  • // can handle unbind event handlers without destroying the scope.
  • // --
  • // NOTE: When a scope is $destroy()'d, it will automatically unbind
  • // all of your event handlers.
  • unbindHandler = $scope.$on( "ping", handlePingEvent );
  •  
  • $scope.isWatchingEvent = true;
  •  
  • }
  •  
  •  
  • // I stop watching for the "ping" event on the scope.
  • function stopWatchingEvent() {
  •  
  • // Invoke the deregistration method in order to unbind the event
  • // handler. Set to null so we know how to handle the "toggle" method.
  • unbindHandler();
  • unbindHandler = null;
  •  
  • $scope.isWatchingEvent = false;
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, when the event is bound, the return value - the deregistration function - is stored. And, when we need to unbind the event, all we do is invoke the deregistration function.




Reader Comments

Hi,

I've taken this a bit further to create a "run once" event handler:

var deregisterRunOnceHandler = $scope.$on('eventName', function() {
[...]
deregisterRunOnceHandler();
deregisterRunOnceHandler = null;
});

Can be useful for others as well.

Regards,

Dirk

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.