Home » Angularjs » How to stop $observe in AngularJS

How to stop $observe in AngularJS

Posted by: admin November 29, 2017 Leave a comment

Questions:

I had recently some issues with jQuery plugins in directives in Angular not being properly cleaned and hence, creating Memory Leaks.

So, today, while I was working on tests to ensure that won’t happen, I realised that there is no way to stop observing.

var stopObserving = attrs.$observe('myProperty', function(newValue) {          
  updateElement(newValue);
});

Since I thought it worked the same way it does on $watch but clearly it doesn’t. According to the docs, $observe will return the callback function, that’s it, the second argument.

I have this test:

describe('destroy',function(){
    beforeEach(function(){
      $scope.$destroy();
    });

    it('should have emptied the DOM node', function(){
      expect(element.text()).toBe('');
    });
    it('shouldn\'t have any more watchers', function(){
      dump(element.data().$scope.$$watchers);
      expect(element.data().$scope.$$watchers.length).toBe(0);
    });
}); 

And it fails, cause there is one watcher. I’ve checked and the $destroy is being called and hence, the cleaning is done. However, how can I get rid of that watcher?

The code, in case you’re curious is here:

https://github.com/firstandthird/angular-popbox

Answers:

AngularJS 1.3 and above

In Angular 1.3 and above, $observe returns the deregister function, so deregistering $observe works exactly as for $watch:

var stopObserving = attrs.$observe(...);
stopObserving();

AngularJS 1.2 and below

In AngularJs 1.2 there is no way to deregister an observer, and, as you rightly noted, $observe returns the callback function.

There is however currently a PR open to change $observe to also return a deregistration function similar to $watch and $on, unfortunately this is only triaged for the 1.3 release because of the breaking change. The PR is over here: https://github.com/angular/angular.js/pull/5609

Good news is it takes a full 3 new lines of code to implement the change, as per the PR, so if you can’t wait until the 1.3 release you can easily implement this yourself.

Questions:
Answers:

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.

visit: http://plnkr.co/edit/nciFRm9HTL3i8xYSUQJa?p=preview

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 console log, you can see that the console.log() statements stop as soon as the “deregistration” function is called.

Questions:
Answers:

as a temporary solution you can do:

var key = 'myAttr';
    attrs.$observe(key, function(newValue) {
    updateElement(newValue);
    delete attrs.$$observers[key];
});

or if your want to be really nifty:

attrs.$observe = function(key, fn) {
    attrs.$observe(key, fn);

    return function() {
       delete attrs.$$observers[key];   
    };
}

var unobserve = attrs.$observe('myAttr', function(newValue){
    updateElement(newValue);
    unobserve();
});