ngIf Why doesn't it clean up its watcher?

421 views
Skip to first unread message

Kamal Bhatt

unread,
Sep 27, 2015, 1:18:22 AM9/27/15
to AngularJS
Hi,
I was looking at the code behind ngIf and I noticed that it doesn't seem to call $scope.$destroy to clean up the watcher it registers:-

https://github.com/angular/angular.js/blob/2a156c2d7ec825ff184480de9aac4b0d7fbd5275/src/ng/directive/ngIf.js

Is there any reason why it does not? Seeing as this a standard directive with a lot of usage, I can only assume that this is intentional.  Can someone explain why it works this way?

Also, I am a bit puzzled by this code:-

        if (block) {
          previousElements = getBlockNodes(block.clone);
          $animate.leave(previousElements).then(function() {
            previousElements = null;
          });
          block = null;
        }


If leave is meant to take an element, why is it been (potentially) given an array.  I am assuming that getBlockNodes is this function:-

    function getBlockNodes(nodes) {
      // TODO(perf): update `nodes` instead of creating a new object?
      var node = nodes[0];
      var endNode = nodes[nodes.length - 1];
      var blockNodes;

      for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
        if (blockNodes || nodes[i] !== node) {
          if (!blockNodes) {
            blockNodes = jqLite(slice.call(nodes, 0, i));
          }
          blockNodes.push(node);
        }
      }

      return blockNodes || nodes;
    }


Thanks.

Kamal.

Sander Elias

unread,
Sep 27, 2015, 5:57:14 AM9/27/15
to AngularJS
Hi Kamal,

Here is a small plunk that shows you the destroy is actually called inside an ngIf. Have a look at the console in there, and toggle the button.
The actual watcher on the ngIf itself is part of the scope that holds the ngIf. Otherwise, it would only fire once.

Regards
Sander

Kamal Bhatt

unread,
Sep 27, 2015, 6:35:22 AM9/27/15
to AngularJS
Thanks Sander for your prompt reply.

I don't think I made myself clear.  In ngIf, there is a watcher defined:-

$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {

          if (value) {
            if (!childScope) {
              $transclude(function(clone, newScope) {
                childScope = newScope;
                clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
                // Note: We only need the first/last node of the cloned nodes.
                // However, we need to keep the reference to the jqlite wrapper as it might be changed later
                // by a directive with templateUrl when its template arrives.
                block = {
                  clone: clone
                };
                $animate.enter(clone, $element.parent(), $element);
              });
            }
          } else {
            if (previousElements) {
              previousElements.remove();
              previousElements = null;
            }
            if (childScope) {
              childScope.$destroy();
              childScope = null;

            }
            if (block) {
              previousElements = getBlockNodes(block.clone);
              $animate.leave(previousElements).then(function() {
                previousElements = null;
              });
              block = null;
            }
          }
        });
           }

Now, it is my understanding that when you register a watcher in a directive/service/controller, you should capture its return and on the current scope's destroy method call it.

I would expect something like this (cut down for brevity):-
      var removeWatcher = $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
         ...
      }
      $scope.$on('destroy', function destroy() { removeWatcher() });


I don't know if this is something I misunderstood or something born out ng-if being a special case born out of it being transclude: 'element'.

Guilherme Meireles

unread,
Sep 27, 2015, 8:14:32 AM9/27/15
to AngularJS
Hi Kamal,

Watchers on the scope are removed when the scope is destroyed so you don't need to manually remove them on scope destruction.

One instance where you would need to remove the watcher is if you are listening to the $rootScope.

var removeWatcher = $rootScope.$watch($attr.ngIf, function ngIfWatchAction(value) {

Kamal Bhatt

unread,
Sep 29, 2015, 4:41:45 AM9/29/15
to AngularJS
Thanks for the clarification.
Reply all
Reply to author
Forward
0 new messages