Unbinding Scope.$on() Event Handlers In AngularJS
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.
Want to use code from this post? Check out the license.
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