Compiling HTML on the Fly via $compileProvider Directive


Summary:

Once again I find myself learning something new with Angular. Today I found out how to effectively use the $compile service to compile an html snippet containing an Angular binding expression. For example, a snippet matching the context of this:

<p>This is a cool example {{myBinding}}.</p>

The use case we had was that a CMS (keystonejs) publishes the content and makes them available on our Mongo DB, and they publish them in the form of HTML that contains binding expressions. So in our response object we contain the HTML snippet with binding expressions and the contents that need to be bound to those binding expressions. Here is what a response object might look like:

content:{ snippet:{'<p>This is a cool example. {{myBinding}}.</p>'}, values: { myBinding: 'I really think so too'} }

This then when received via Angular service we render it to the page, and the binding expression is then rendered like so:

'This is a cool example. I really think so too'

The Problem

So Angular the angular views that are inserted into the `ng-view' directive are compiled on insertion into the dom. In other words, the views, or templateUrls, are injected into the dom when the ngRoute (defined in the Angular router file) is called. This router also binds all the bindable tags with the Angular controller file associated with the route. The HTML compile process does a number of other things, such as locates all directives in dom and link functions and to those directives. For more info on the compiling process take a look at the Angular docs found here. So problem lies in the timing of when this compile process occurs, since we want to compile again after the first compile process to render our new binding expressions.

The Solution

So at first I was trying to see if you could actually recompile on an already compiled HTML file, so I asked on StackOverflow. Here is the link to my question. So thanks to user Chandermani he pointed me in the right direction, $compile service located in at the bottom of the Angular documentation found here. At first I had some confusion of the compile service and how to use it because the documentation for $compile uses a compile tag in their Directive Definition Object (DDO). So after creating a DDO and realizing that this wasn't the correct solution I directed my attention to the example at the bottom of the documentation page. There isnt much information on what the code in the example is doing but I derived that this was exactly what I needed to do. So I created a directive and here it is:

angular.module('angularCompile', [], function($compileProvider) {
// configure new 'angularCompile' directive by passing a directive
// factory function. The factory function injects the '$compile'
$compileProvider.directive('angularCompile', function($compile) {
    // directive factory creates a link function
    return function(scope, element, attrs) {
        scope.$watch(
            function(scope) {
                // watch the 'angular-compile' expression for changes
                return scope.$eval(attrs.angularCompile);
            },
            function(value) {
                // when the 'angular-compile' expression changes
                // assign it into the current DOM
                element.html(value);

                // compile the new DOM and link it to the current
                // scope.
                // NOTE: we only compile .childNodes so that
                // we don't get into infinite loop compiling ourselves
                $compile(element.contents())(snippet);
            }
        );
    };
})

});

The above code demonstrates two important tasks. First, notice how the directive is created off of the $compileProvider service in Angular, and our new directive is called angularCompile, which is translated as angular-compile in the HTML. Second, which is the most important, notice the scope.$watch that takes two functions as arguments. The first argument of the watch is indicates what field to listen on. In our case this is listens on the element tag name we created for the directive. We use the $eval service to render the html of the snippet which lets the listner know what exactly to listen on. The second arugument is a function which is only called when there is an inequality of the watched expression listed in the first function and its value when its changed. This function is basically where you would put the logic of what you want your watcher to do when the listener event fires. Now what we want the 'to do' function to do is when the directive's tag changes we want to update the view using the $compile service. This will then re compile our new snippet retrieved from our back-end. Here is what I have for my HTML using our new angular-compile directive.

The new_snippet refers to what binding you have in your controller for this directive. For example:

 controller('Ctrl',function ($route, $scope, $Content) {

    var getContent = $Content.get({slug: 'home-bottom-left'
    }, function () {
        //bind the new_snippet value with the response from service
        //call
        $scope.new_snippet = leftMainContent.response.content.snippet;
    });
});

And thats pretty much it. You know have a directive that will listen on the HTML element you defined in your directive and perform a compile on whatever the value is. Hope this helps

Winston Logger in NodeJS - Chiiillll Winston, I'm Just Blogging on Logging
Create a Dynamic Modal Directive in Minutes using Angular and UI Bootstrap
NPM/Bower Link - Make Friends with Your Current Dev Node Modules and Bower Components Today