If you have used AngularJS, you probably have at least once used the Scope.$on or Scope.$watch methods, and probably had to dispose of listeners / watchers. Here’s a little trick you may like to do just that in a few lines.

Deregistration functions are awkward

AngularJS’s Scope.$on and Scope.$watch methods return a deregistration function which should be called when you don’t need the callback anymore (e.g. if you were listening on $rootScope from a controller), mainly for performance reasons. Not disposing of listeners will keep the garbage collector from doing its job. For instance, if the fooListener below was to use a variable (say counter) from a higher context, it wouldn’t be freed from memory. This can lead to a large consumption of RAM, and is especially something to watch for in Single-Page Applications and mobile apps.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// These will only be freed when the $rootScope gets destroyed, likely
// when you close the tab / reload the page. They will keep piling up if
// your controller is created multiple times, and are a common cause of
// memory leaks.
var deregisterFooListener = $rootScope.$on('myFooEvent', fooListener);
var deregisterBarListener = $rootScope.$on('myBarEvent', barListener);
var deregisterBazListener = $rootScope.$on('myBazEvent', bazListener);

// This one will be automatically freed when the $scope gets destroyed.
$scope.$on('$destroy', deregister);

// This variable would be leaking if we didn’t call the deregistration
// function because it is used in the fooListener.
var counter = 0;

// A dummy listener for the example’s sake
function fooListener () {
// Increment the counter
counter = counter + 1;
}

function deregister () {
// The scope’s about to be destroyed, we don’t need the listeners anymore.
deregisterFooListener();
deregisterBarListener();
deregisterBazListener();
}

Use an array!

This is OK when you only have one or two listeners to care for, but gets messier the more you have. My humble tip: use an array of deregistration functions which you can loop on and call one by one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var listeners = [
$rootScope.$on('myFooEvent', fooListener),
$rootScope.$on('myBarEvent', barListener),
$rootScope.$on('myBazEvent', bazListener)
];

$scope.$on('$destroy', deregister);

function deregister () {
while (listeners.length) {
// Mind the double parens! We pop one deregistration function at a time
// and call it immediately.
listeners.pop()();
}
}

That way you won’t pollute the local scope and you won’t have to come up with awkward variable names such as deregisterStateChangeSuccessListener.

Deregistration as a Service

You don’t want to repeat yourself and copy-paste this deregister function all around the codebase, do you? Enter the deregister service.

1
2
3
4
5
6
7
8
9
10
11
12
angular
.module('app.utils')
.value('deregister', deregister);

function deregister (listeners) {
return function deregisterCallback () {
// Feel free to loop however you want, I like this one for brevity
while (listeners.length) {
listeners.pop()();
}
};
}

And then you can simply use it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Inject the deregister service
function MyController ($scope, deregister) {
$scope.$on('$destroy', deregister([
$rootScope.$on('myFooEvent', fooListener),
$rootScope.$on('myBarEvent', barListener),
$rootScope.$on('myBazEvent', bazListener)
]));

// or, if it’s more your style:
var deregisterEverything = deregister([
$rootScope.$on('myFooEvent', fooListener),
$rootScope.$on('myBarEvent', barListener),
$rootScope.$on('myBazEvent', bazListener)
]);

$scope.$on('$destroy', deregisterEverything);
}

Much cleaner, isn’t it?