We have this directive that allows developers to specify their own template and data that will be used for the scope that will be bound to that template by using $compile. The template will likely to have angular expressions which will create watchers.
When the element's content that is represented by the template and scope is removed from the DOM tree, how do I remove the watchers that were created with it?
This link proves that watchers stay even when the DOM it represent is removed from the tree.
Clicking the compile button the first time will compile your angular template and attach it to the DOM tree. The second time the button is clicked, it will empty the 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="[email protected]" data-semver="1.7.2" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script data-require="[email protected]" 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;
});
};
}]);
As mentioned by TGH, the watchers are normally removed when the DOM and scope it lives in is destroyed. If this isn't happening for you, then you can get a reference to the element's scope and destroy the scope manually, removing the watchers.
Irrelevant code is 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 have already have this functionality in a directive, it's much cleaner when done in its link function. Listen for when the element is removed from the DOM and clean up the scope accordingly.
link: function (scope, element, attrs) {
element.on("$destroy", function () {
scope.$destroy();
})
}
If the DOM and associated scope is destroyed, the watchers for that content will be gone as well.
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