Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Loveena Dsouza

Unbinding $watch() Listeners In AngularJS

By Ben Nadel on

In AngularJS, you can watch for changes in your view-model by binding a listener to a particular statement (or function) using the $scope's $watch() method. You tell the $watch() method what to examine and AngularJS will invoke your listener whenever the item-under-scrutiny changes. In the vast majority of cases, the $watch() statement can be run with a set-and-forget mentality. But, in rare cases, you may only want to $watch() for a single change; or, only watch for changes up to a certain point. If that's the case, you can unbind a $watch() listener using the provided "deregistration" function.


 
 
 

 
  
 
 
 

When you invoke the $watch() method, to create a binding, AngularJS returns a "deregistration" function. This function can then be used to unbind your $watch() listener - all you have to do is invoke this returned function and your $watch() listener will be removed.

To see this in action, take a look at the following code. In this demo, we're watching the number of clicks that a link receives. And, if that number gets above 5, we're going to show a message; however, once the message is shown, we remove the listener as it will no longer have any value.

  • <!doctype html>
  • <html ng-app="Demo" ng-controller="AppController">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Unbinding $watch() Listeners In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Unbinding $watch() Listeners In AngularJS
  • </h1>
  •  
  • <p>
  • <a ng-click="incrementCount()">Click it, click it real good!</a>
  • &raquo;
  • {{ clickCount }}
  • </p>
  •  
  • <p ng-show="isShowingFeedback">
  • <em>Heck yeah, now that's how you click a link!!</a>
  • </p>
  •  
  •  
  •  
  • <!-- Load jQuery and AngularJS from the CDN. -->
  • <script
  • type="text/javascript"
  • src="//code.jquery.com/jquery-2.0.0.min.js">
  • </script>
  • <script
  • type="text/javascript"
  • src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js">
  • </script>
  • <script type="text/javascript">
  •  
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define the root-level controller for the application.
  • app.controller(
  • "AppController",
  • function( $scope ) {
  •  
  • // I keep track of how many times the user has clicked
  • // the link.
  • $scope.clickCount = 0;
  •  
  • // I determine if the "encouraging" feedback is shown.
  • $scope.isShowingFeedback = false;
  •  
  •  
  • // ---
  • // WATCHERS.
  • // ---
  •  
  •  
  • // When you call the $watch() method, AngularJS
  • // returns an unbind function that will kill the
  • // $watch() listener when its called.
  • var unbindWatcher = $scope.$watch(
  • "clickCount",
  • function( newClickCount ) {
  •  
  • console.log( "Watching click count." );
  •  
  • if ( newClickCount >= 5 ) {
  •  
  • $scope.isShowingFeedback = true;
  •  
  • // Once the feedback has been displayed,
  • // there's no more need to watch the change
  • // in the model value.
  • unbindWatcher();
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I increment the click count.
  • $scope.incrementCount = function() {
  •  
  • $scope.clickCount++;
  •  
  • // NOTE: You could just as easily have called a
  • // counter-related method right here to test when
  • // to show feedback. I am just demonstrating an
  • // alternate approach.
  •  
  • };
  •  
  • }
  • );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, we're storing the function reference returned by the $watch() statement; then, once the $watch() fires a few times, we invoke that stored method, unbinding the $watch() listener. If you watch the video, you can see that the console.log() statements stop as soon as the "deregistration" function is called.

Most of the time, you won't need to use this. In fact, I only found out about this feature yesterday when I ran into a situation in which I needed to unbind the $watch() listener. That said, it turns out it's rather easy, as long as you know it's there.

Tweet This Interesting post by @BenNadel - Unbinding $watch() Listeners In AngularJS Thanks my man — you rock the party that rocks the body!



Reader Comments

Cool! "Unwiring" things (like watches and jQuery plugins) has been one of the more difficult things for me to wrap my head around in Angular.

You can use deregistration with events too:

  •  
  • $scope._one = function(event, fn, scope) {
  • var _scope = scope || $rootScope;
  • var deregister = _scope.$on(event, function(){
  • fn.apply(this, arguments);
  • deregister();
  • });
  • };
  •  
  • $scope._one('$routeChangeSuccess', doSomethingOnce);

Reply to this Comment

@Caleb,

Oh very cool - I didn't realize you could do that with event bindings as well. As I've gotten into AngularJS, one thing that I have found very frustrating, with regards to unbinding, is that most jQuery plugins don't have any concept of a "destroy" or "teardown" method. So, I can bind to the "$destroy" event in my directives, but I can't always figure out how to gracefully destroy a jQuery plugin.

Most of the time, it's OK because the plugin bindings is local; however, if a jQuery plugin binds to the Document or Window (or something else that sticks around for a while), I can't clean it up!

Reply to this Comment

@Sampath,

I'm not sure what ngGrid is - it's not in the documentation. Must be some add-on or something; I'll have to look it up.

Reply to this Comment

Great post!

Do you know if Angular takes care of the listeners (deregister them) after a controller lifecycle?

What happens, for example, if a Controller (Ctrl1) registers a listener, you browse to another Controller and then you browse to the Ctrl1 again? would it deregister and then reregister the Ctrl1 listeners again?

Thanks!

Reply to this Comment

Great post! I am now using this method as a way to run two functions synchronously (run function B only after function A has returned), although I'm not sure this is a good idea from a best-practice perspective. Any thoughts on doing this?

Reply to this Comment

Oi! First i was "what is this witchcraft!?" and then i watched the video and at 2:10 i found my answer :)
Thanks for the tip on very neat way to solve this.

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.