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
$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)
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).
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.
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.
In Angular 1.3 and above, $observe returns the deregister function, so deregistering $observe works exactly as for $watch:
var stopObserving = attrs.$observe(...);
stopObserving();
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.
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.
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();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With