Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop $observe in AngularJS

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

like image 654
Antonio Laguna Avatar asked Jan 04 '14 20:01

Antonio Laguna


People also ask

What is $observe in AngularJS?

$observe/$watch(value : string, callback : function); value : is always a string reference to the watched element (the name of a scope's variable or the name of the directive's attribute to be watched) callback : the function to be executed of the form function (oldValue, newValue)

What is$ scope in AngularJS?

The $scope in an AngularJS is a built-in object, which contains application data and methods. You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML).

How many child scopes can an AngularJS application have?

Scope Hierarchies Every angularJS application can have only one root scope as it is the parent scope. But it can have more than one child scope. New scopes can be created by directives which are added to parent scope as child scope.

What is scope hierarchy in Angular?

Scope hierarchyEach Angular application has a root scope and can have any number of child scopes. The root scope is created whenever the Angular application is created, but then, directives cam create new child scopes. When a new child scope is created it is added as a child of his parent scope.


3 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.

like image 187
Beyers Avatar answered Oct 18 '22 03:10

Beyers


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.

like image 32
Pranav Avatar answered Oct 18 '22 05:10

Pranav


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();
});
like image 2
yohairosen Avatar answered Oct 18 '22 03:10

yohairosen