Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should angular $watch be removed when scope destroyed?

Currently working on a project where we found huge memory leaks when not clearing broadcast subscriptions off destroyed scopes. The following code has fixed this:

var onFooEventBroadcast = $rootScope.$on('fooEvent', doSomething);  scope.$on('$destroy', function() {     //remove the broadcast subscription when scope is destroyed     onFooEventBroadcast(); }); 

Should this practice also be used for watches? Code example below:

var onFooChanged = scope.$watch('foo', doSomething);  scope.$on('$destroy', function() {     //stop watching when scope is destroyed     onFooChanged(); }); 
like image 290
Andrew Avatar asked Aug 04 '14 07:08

Andrew


People also ask

What is $Watch in angular?

What is the angular JS watch function? The angular JS $watch function is used to watch the scope object. The $watch keep an eye on the variable and as the value of the variable changes the angular JS $what runs a function. This function takes two arguments one is the new value and another parameter is the old value.

What is the use of $Watch in AngularJS?

$watch() function is used to watch the changes of variables in $scope object. Generally the $watch() function will create internally in Angularjs to handle variable changes in application.

What does scope do in angular?

AngularJS Scope The scope is the binding part between the HTML (view) and the JavaScript (controller). The scope is an object with the available properties and methods. The scope is available for both the view and the controller.

When on Destroy is called in AngularJS?

@setec . Yes you are right, $destroy() should be called only when a child scope has been created manually. Also, in most cases the child scope is created automatically every time we use Directives. (e.g ng-controller, ng-app) and hence $destroy() function is called automatically.


Video Answer


2 Answers

No, you don't need to remove $$watchers, since they will effectively get removed once the scope is destroyed.

From Angular's source code (v1.2.21), Scope's $destroy method:

$destroy: function() {     ...     if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;     if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;     if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;     if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;     ...     this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];     ... 

So, the $$watchers array is emptied (and the scope is removed from the scope hierarchy).

Removing the watcher from the array is all the unregister function does anyway:

$watch: function(watchExp, listener, objectEquality) {     ...     return function deregisterWatch() {         arrayRemove(array, watcher);         lastDirtyWatch = null;     }; } 

So, there is no point in unregistering the $$watchers "manually".


You should still unregister event listeners though (as you correctly mention in your post) !

NOTE: You only need to unregister listeners registered on other scopes. There is no need to unregister listeners registered on the scope that is being destroyed.
E.g.:

// You MUST unregister these $rootScope.$on(...); $scope.$parent.$on(...);  // You DON'T HAVE to unregister this $scope.$on(...) 

(Thx to @John for pointing it out)

Also, make sure you unregister any event listeners from elements that outlive the scope being destroyed. E.g. if you have a directive register a listener on the parent node or on <body>, then you must unregister them too.
Again, you don't have to remove a listener registered on the element being destroyed.


Kind of unrelated to the original question, but now there is also a $destroyed event dispatched on the element being destroyed, so you can hook into that as well (if it's appropriate for your usecase):

link: function postLink(scope, elem) {   doStuff();   elem.on('$destroy', cleanUp); } 
like image 98
gkalpak Avatar answered Sep 21 '22 00:09

gkalpak


I would like to add too @gkalpak's answer as it lead me in the right direction..

The application I was working on created a memory leak by replacing directives whom had watches. The directives were replaced using jQuery and then complied.

To fix i added the following link function

link: function (scope, elem, attrs) {     elem.on('$destroy', function () {         scope.$destroy();     }); } 

it uses the element destroy event to in turn destroy the scope.

like image 29
Kieran Avatar answered Sep 22 '22 00:09

Kieran