Saturday, October 11, 2014

AngularJS - Be careful dynamically setting content using $compile



I was recently working on a directive that dynamically generated new content at runtime based on user interaction, and the new content sometimes contained other directives that needed to be recognized. To get this to be properly "angularized", I used the $compile() method manually. Tada, the new content and sub directives were all linked perfectly. However, I noticed whenever I replaced that content subsequent times, my sub directives were not getting destroyed...a huge concern for memory leaks, and it also meant that all of the content that didn't have a sub-directive, well this would not be destroyed properly either. So I started digging into it...


Here's my explanation:

When creating a directive, imagine you have a situation where, based on certain conditions, you want to call render one existing template or another. Some solutions use dynamic templates via "ng-include" in the template declaration itself, but this isn't great for responding to user interaction. So instead, we get to straight down to it and invoke the $compile() method.


The $compile() method takes a template, turns that template into a function, and returns the function. That function is then invoked against a scope. Now, here's where we can get into trouble. Take an example:


link: function(scope, element, attrs) {

    //Some user interaction
    scope.changeContent(newTemplate) {


    //Retrieve the template from $templateCache
    var tpl = $templateCache.get(newTemplate);

    //Generate the angularized HTML code, attached to the scope
    var html = $compile(tpl)(scope);

    //Replace the contents of the current directive element with the new HTML
    element.empty().append(html);


    scope.on('$destroy', function() {
        //Cleanup code here

    });
}


This is all good now, but if our directive is something that will be many times throughout the app, we need to be careful with this, because this will cause a memory leak on subsequent calls to the "changeContent" method. The reason is that the template function created with the $compile() method is now attached to your scope, and this scope is not going to be destroyed before running it again.


Luckily, there is an easy fix. You create an isolated child scope from the current scope, keep a reference to it, and then you can destroy the child scope before creating a new one.
Here's how you might do it:


link: function(scope, element, attrs) {
    var childScope = null;

    //Some user interaction
    scope.changeContent(newTemplate) {

        //Destroy the old child scope
        if (childScope != null) {
           childScope.$destroy();
        }

    //Create  a new isolated scope that inherits from our current scope
    childScope = scope.$new();

    //Retrieve the template from $templateCache
    var tpl = $templateCache.get(newTemplate);

    //Generate the angularized HTML code, attached to the new child scope
    var html = $compile(tpl)(childScope);

    //Replace the contents of the current directive element with the new HTML
    element.empty().append(html);


    scope.on('$destroy', function() {
        //Cleanup code here

        // This is not necessary as the child scope will be destroyed when this scope is destroyed, BUT, it makes me feel better to see it happening.

        if (childScope != null) {
            childScope.$destroy();
            childScope = null;
        }

    });
}

1 comment:

  1. In comparision with many other blogs about the angularjs out there, this is completely different which has made me completely attracted to this site for the information on data science . I only learned subject like this at online Angularjs training center earlier. Thanks for your co-operation.

    ReplyDelete