I want to dynamically create angular components using javascript, and then have angular compile them using $compile
with a newly created scope. Then when I have no longer use for that component, I want to destroy the component and the new scope.
Everything works as expected, except from the fact that even though I am destroying the new scope, all the memory that it uses is never released.
Here is part of a simplified version of that code:
app.controller("mainCtrl", ["$scope", "$compile", function($scope, $compile) {
var childScope;
//call this every time the button is clicked
this.createDirective = function() {
//dynamically create a new instance of the custom directive
var customDirective = document.createElement("custom-directive");
//if another child scope exists, destroy it
if (childScope) {
childScope.$destroy();
childScope = undefined;
}
//create a new child scope
childScope = $scope.$new();
//compile the custom directive
$compile(customDirective)(childScope);
};
}]);
Full working example of this code is here
All this code does, is create a new component every time the button is clicked, but first destroy any component that already exists. Notice that I am not actually adding the compiled component in the page, because I noticed that the leak was still there regardless of whether I used it or not.
Using Chrome's development tools (Profiles -> Record Allocation Timeline -> Start) I see the following memory usage after clicking the button a few times:
Memory consumption
It is clear that any memory occupied by the customDirective is never actually released, even though the scope's $destroy
function is being called.
I have successfully used $compile
in the past without creating a new scope, but it seems that I am missing something in this scenario. Should I be doing something else as well to make sure that there are no references to the new scope?
Based on the answer below by JoelCDoyle, here is the fix (I add an on destroy function to the scopes I create):
app.controller("mainCtrl", ["$scope", "$compile", function($scope, $compile) {
var childScope;
//call this every time the button is clicked
this.createDirective = function() {
//dynamically create a new instance of the custom directive
var customDirective = document.createElement("custom-directive");
//if another child scope exists, destroy it
if (childScope) {
childScope.$destroy();
childScope = undefined;
}
//create a new child scope
childScope = $scope.$new();
//compile the custom directive
var compiledElement = $compile(customDirective)(childScope);
//FIX: remove the angular element
childScope.$on("$destroy", function() {
compiledElement.remove();
});
};
}]);
Fixed fiddle
A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code (i.e. unreachable memory). A memory leak has symptoms similar to a number of other problems and generally can only be diagnosed by a programmer with access to the program's source code.
To avoid memory leaks, memory allocated on heap should always be freed when no longer needed.
No it does not cause memory leaks.
Stack memory leaks occur when a method keeps getting called but never exits. This can happen if there is an infinite loop or if the method is being called with different data each time but the data is never used. Eventually, the stack will fill up and the program will run out of memory.
I think I've found a solution to this: https://jsfiddle.net/yqw1dk0w/8/
app.directive('customDirective', function(){
return {
template: '<div ng-controller="customDirectiveCtrl"></div>',
link: function(scope, element) {
scope.$on('$destroy', function() {
element.remove();
});
}
};
});
I'm still a little fuzzy on why this works, but this section, How Directives are Compiled, in angular compile docs provides a clue: https://docs.angularjs.org/guide/compiler
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners on the elements and setting up $watchs with the scope as each directive is configured to do.\
The result of this is a live binding between the scope and the DOM. So at this > point, a change in a model on the compiled scope will be reflected in the DOM.
Destroying the scope, I'm guessing, does not remove these element listeners. That's what the above code is doing: destroy directive/child scope on scope destroy
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