kapoiosKapou kapoiosKapou - 1 month ago 17
AngularJS Question

Memory leak when using angular's $compile with a new scope

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?

Edit



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

Answer

I think I've found the answer 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();
      });
    }
  };
});

Destroying the scope does not remove the DOM node. That's what the above code is doing: destroy directive/child scope on scope destroy

Comments