Memory leak when manually compiling directives in

2019-07-19 12:17发布

问题:

I'm trying to manually compile a directive and add it to the DOM via JQuery. The directive is a simple div with an ngClick handler. No JQuery plugins are used in the directive itself (which seems to be the focus of many of the other memory leak threads).

If you run a profiler you will find that it leaks nodes. Is there something that can be done to fix this or is it a problem in JQuery/Angular?

Fiddle here
Profiler screenshot

HTML

<div ng-app="TestApp">
    <buttons></buttons>
    <div id="container"></div>
</div>

Javascript

var ButtonsCtrl = function($scope, $compile) {
    this.scope = $scope;
    this.compile = $compile;
};

ButtonsCtrl.prototype.toggle = function() {
    var c = angular.element('#container').children();

    if (0 in c && c[0]) {
        c.scope().$destroy();
        c.remove();
    } else {
        var s = this.scope.$new();
        this.compile('<thing color="blue"></thing>')(s).appendTo('#container');
    }
};

var ThingCtrl = function($scope) {};
ThingCtrl.prototype.clicky = function() {
    alert('test');
};

var module = angular.module('components', []);
module.directive('buttons', function() {
    return {
        restrict: 'E',
        template: '<button ng-click="ctrl.toggle()">toggle</button>',
        controller: ButtonsCtrl,
        controllerAs: 'ctrl'
    }
});

module.directive('thing', function() {
    return {
        restrict: 'E',
        scope: {
            color: '@'
        },
        template: '<div style="width:50px;height:50px;background:{{color}};" ng-click="ctrl.clicky()"></div>',
        controller: ThingCtrl,
        controllerAs: 'ctrl'
    };
});

angular.module('TestApp', ['components']);

回答1:

If you cache the template function that $compile returns, then call that with a new scope, the memory leak seems to decrease to only one node per click.

var ButtonsCtrl = function($scope, $compile) {
    this.scope = $scope;
    this.makeThing = $compile('<thing color="blue"></thing>');
    this.thingScope = null;
};

Additionally, removing the jQuery wrappers from the ButtonsCtrl.toggle() method appears to eliminate the node leak altogether.

ButtonsCtrl.prototype.toggle = function() {
    var container = document.getElementById('container');

    if (this.thingScope) {
        container.removeChild(container.childNodes[0]);
        this.thingScope.$destroy();
        this.thingScope = null;
    } else {
        this.thingScope = this.scope.$new();
        container.appendChild(this.makeThing(this.thingScope)[0]);
    }
};

See what you think.