How can I remove observers created by a template that has been added and removed dynamically by compiling $?

We have this directive, which allows developers to specify their own template and data to be used for the scope, which will be bound to this template by compiling $. The template will probably have angular expressions that will create observers.

When the content of an element, represented by the template and scope, is removed from the DOM tree, how do you remove the observers that were created with it?

This link proves that observers remain even when the DOM it represents is removed from the tree.

By clicking the compile button, you will compile your angular template for the first time and attach it to the DOM tree. The second time the button is clicked, it will be an empty element where the previous template was added and add the newly compiled template.

index.html

<!DOCTYPE html>
<html ng-app="app">
  <head>
    <script data-require="jquery@1.7.2" data-semver="1.7.2" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script data-require="angular.js@1.2.6" data-semver="1.2.6" src="https://code.angularjs.org/1.2.6/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="smart-table-debug.js"></script>
    <script src="script.js"></script>
  </head>

  <body ng-controller="controller">
    <h1>Compile Demo</h1>

    HTML to compile<br/>
    <textarea id="html" ng-model="html"></textarea>
    <br/>

    Result<br/>
    <div id="result"></div>
    <br/>
    <input type="button" value="compile" ng-click="compile()" />
    <br/>
    Compile time: {{ compileTime }}<br/>
    Watchers count: {{ watchersCount }}<br/>
    Digest count: {{ digestCount }} <br/>
    Total time taken: {{ totalTime }} milliseconds <br/>
  </body>
</html>

      

script.js

function getAllScopes( rootScope, allScopes ) {
  if( !allScopes ) {
    allScopes = [];
  }
  allScopes.push( rootScope );

  for( var scope = rootScope.$$childHead; scope; scope = scope.$$nextSibling ) {
    getAllScopes( scope, allScopes );
  }

  return allScopes;
}

angular.module( "app", [] )
.controller( "controller", ["$scope", "$compile", "$rootScope", "$timeout", function($scope, $compile, $rootScope, $timeout ){

  var e = angular.element;

  $scope.html = "<div>{{watchersCount}}</div>"
  $scope.watchersCount = 0;
  $scope.digestCount = 0;
  $scope.compileClickStart = performance.now();
  $scope.totalTime = 0;

  /* because we didn't gave watch expression as 1st parameter but a functionn,
   * the function will be called at every end of digest cycle.
   * https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest
   */
  $rootScope.$watch(function() {
    $scope.digestCount += 1;
    $scope.totalTime = performance.now() - $scope.compileClickStart;
  });

  $scope.compile = function(){
    $scope.digestCount = 0;
    $scope.compileClickStart = performance.now();

        var insertTarget = e('#result');
        var targetScope = $scope.$new();

        insertTarget.empty();
        t0 = performance.now();
        var expandedContent = $compile($scope.html)(targetScope);
        t1 = performance.now();
        $scope.compileTime = (t1 - t0) + " milliseconds.";
        insertTarget.append( expandedContent );

        $timeout( function() {
            var allScopes = getAllScopes($rootScope);
            var allWatchers = [];
            for( var i = 0; i < allScopes.length; i++ ) {
              var scope = allScopes[i];
              if( scope.$$watchers) {
                //allWatchers = allWatchers.concat( scope.$$watchers );
              for( var j = 0; j < scope.$$watchers.length; j++ ) {
              allWatchers.push({
                            "scope" : scope,
                            "watcher" : scope.$$watchers[j] 
                        });
              }
              }
            }
            console.log( allScopes );
            $scope.watchersCount = allWatchers.length;
        });
  };

}]);

      

+3


source to share


2 answers


As mentioned by TGH, observers are usually removed when the DOM and the scope it lives in are destroyed. If that doesn't happen for you, you can get a reference to the element's area and destroy the area manually by removing the observers.

Invalid code has been omitted for brevity.

var expandedContent;

$scope.compile = function(){
  // ...

  var insertTarget = e('#result');
  var targetScope = $scope.$new();

  if (expandedContent) {
    expandedContent.scope().$destroy();
  }
  insertTarget.empty();

  expandedContent = $compile($scope.html)(targetScope);
  insertTarget.append( expandedContent );

  // ...
}

      



If you already have this function in a directive, it is much cleaner when executed in its link function. Listen for when an element is removed from the DOM and clean up the area accordingly.

link: function (scope, element, attrs) {
  element.on("$destroy", function () {
    scope.$destroy();
  })
}

      

+5


source


If the DOM and associated region are destroyed, observers for that content will also disappear.



+2


source







All Articles