I am running into a big memory leak when using a custom DataTables directive. The directive uses Jquery’s DataTables plugin and there is one function that causes the memory leak.
var rowCompiler = function(nRow, aData, iDataIndex) {
nRow = $compile(nRow)(scope);
}
If I comment out this method then I do not see the leak. The rowCompiler method takes the first column and replaces the ID with id. This column then has two directives so I need to use $compile to let Angular know about the directives. I have tried using scope.$on('$destroy', function() but the nodes and memory keeps going up.
Below is the rendered HTML from Data-tables.
<tr class="ng-scope odd">
<td class=""><ng-menu-drop ng-idr-id="630" class="ng-isolate-scope">
<div class="menu-drop">
<li class="dropdown" style="list-style:none;"> <a class="dropdown-toggle"><i class="icon-cog icon-large"></i> </a>
<ul class="dropdown-menu">
<li><a href="#/idr/630/profile/view">Profile</a></li>
<li></li>
<li> <a href="#/idr/630/documents">Documents</a></li>
<li><a href="#/idr/630/communication">Communication </a> </li>
<li><a href="#/idr/630/hierarchy/">Hierarchy </a></li>
</ul>
</li>
</div>
</ng-menu-drop>
</td>
<td class=""><div tooltip-placement="left" tooltip="test" class="ng-scope">test</div></td>
.directive('ngMenuDrop', [ function(scope, elm, attrs) {
return{
restrict:"E,A",
scope: {
ngIdrId: '@'
},
link: function (scope, element, attrs) {
},
template: '<div class="menu-drop"><li class="dropdown" style="list-style:none;"><a class="dropdown-toggle"><i class="icon-cog icon-large"></i> </a> ' +
' <ul class="dropdown-menu" ><li><a href="#/idr/{{ngIdrId}}/profile/view">Profile</a><li><li><a href="#/idr/{{ngIdrId}}/documents">Documents</a></li><li><a href="#/idr/{{ngIdrId}}/communication">Communication </a></li><li><a href="#/idr/{{ngIdrId}}/hierarchy/">Hierarchy </a></li></ul></li></div>'
}
}])
When you call $compile, you need to make sure that you are not compiling an element that has already been compiled. Doing so may result in memory leaks when the element is compiled more than once and their $watches aren't cleaned up.
If you are compiling part of the DOM tree and finding that you are compiling it a second time, then more than likely there is a better way to structure your templates. For example, you could create a template using angular.element('...'), or using jquery, instead of relying on 'template' property in the directive definition. Also, if you are re-compiling transcluded contents, then you should find a way so that the compilation happens once.
Sometimes node leaks are caused by javascript event handlers. In directives pertaining to the row elements, look for jQuery or jqLite event listeners that may be attached. When the row gets removed, these need to be removed/dereferenced. You can listen for a '$destroy' DOM event. This is different than Angular events (scope.$on).
The documentation says:
$destroy - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM element before it is removed.
In an event handler, you can remove references to nodes, cancel timeouts and intervals, etc.
element.on('$destroy', function () {/* cleanup */});
I believe pixelbits is on the right track. An answer I submitted recently and its accompanying jsfiddle demonstrated that caching the linking function $compile returns substantially reduced the node leak. So, as pixelbits says, try $compile-ing your template only once, then save the resulting link function and call it on on the scopes you want to bind to your compiled directive.
I cannot tell you much about the internals of what Angular is doing with nodes in $compile other than the previously referenced documentation explaining that the compilation process involves cloning the template elements, but maybe the other question, answer, and fiddle can point you in the right direction.