Compile, Pre, and Post Linking in AngularJS

Wednesday, May 28, 2014

Explanations of the compile and link functions in an AngularJS directive don’t often come with examples. Let’s look at a directive named simple which appears in the following markup. You can also play along  in this plunk. Open the browser’s developer tools to see the logging output. 

<div ng-repeat="i in [0,1,2]">
    <simple>
        <div>Inner content</div>
    </simple>
</div>

Notice the directive appears once inside an ng-repeat and will need to render three times. The directive also contains some inner content.

What we want to focus on is how and when the various directive functions execute, as well as the arguments to the compile and linking functions.

To see what happens we’ll use the following shell of a directive definition.

app.directive("simple", function(){
   return {
     restrict: "EA",
     transclude:true,
     template:"<div>{{label}}<div ng-transclude></div></div>",

     compile: function(element, attributes){

         return {
             pre: function(scope, element, attributes, controller, transcludeFn){

             },
             post: function(scope, element, attributes, controller, transcludeFn){

             }
         }
     },
     controller: function($scope){

     }
   };
});

Compile Executes Once

Element in Angular Compile FunctionThe first function to execute in the simple directive during view rendering will be the compile function. The compile function will receive the simple element as a jqLite reference, and the element contents will look like the content in the picture to the right. 

Notice how Angular has already added the directive template, but has not performed any transclusion or setup the data binding.

At this point it is safe for the code inside the compile function to manipulate the element, however it is not a place where you want the code to wire up event handlers. The element passed to compile in this scenario will be an element that the framework clones three times because we are working inside an ngRepeat. It will be the clones of this element the framework places into the DOM, and these clones are not available until the linking functions start to run. The idea behind the compilation step is to allow for one time DOM manipulation before the cloning – a performance optimization.

This compile function in the sample above returns an object with the pre and post linking functions. However, many times we don’t need to hook into the compilation phase, so we can have a link function instead of a compile function.

app.directive("simple", function(){
    return {
       //... 
        link: function(scope, element, attributes){

        },
        controller: function($scope, $element){

        }
    };
});

A link function will behave like the post-link function described below.

Loop Three Times

Since the ngRepeat requires three copies of simple, we will now execute the following functions once for each instance. The order is controller, pre, then post.

Controller Function Executes

The first function to execute for each instance is the controller function. It is here where the code can initialize a scope object as any good controller function will do.

Note the controller can also take an $element argument and receive a reference to the simple element clone that will appear in the DOM.

The element will look just like the element in the previous picture because the framework hasn’t performed the transclusion or setup data binding, but it is the element that will live in the DOM, unlike the element reference in compile.

However, we try to keep controllers from referencing elements directly. You generally want to limit direct element interaction to the post link function.

Pre-link Executes

Element In Angular Pre LinkBy the time we reach the pre-link function (the function attached to the pre property of the object returned from compile), we’ll have both a scope initialized by the controller function, and a reference to a real element that will appear in the DOM.

However, we still don’t have transcluded content and the template isn’t linked to the scope because the bindings aren’t setup.

The pre link function is only useful in a couple special scenarios, which is why you can return a function from compile instead of an object and the function will be considered by the framework as the post link function.

Post-link Executes

Element in AngularJS Post LinkPost link is the last function to execute. Now the transclusion is complete, the template is linked to a scope, and the view will update with data bound values after the next digest cycle .

In post-link it is safe to manipulate the DOM, attach event handlers, inspect child elements, and setup observations on attributes and watches on the scope.

Summary

Directives have many mysterious features when you first come across them, but with some time and experiments like these you can at least figure out the working pieces. As always, the harder part is knowing how to apply this knowledge to the real components you need to build. More on that in the future.


Comments
gravatar Jon Friday, May 30, 2014
I always have to re-discover the directive processing system every time I go back to write a new one. This is one of the clearer explanations of what is available, where, and in what order. Thanks!
gravatar Mark Foote Friday, May 30, 2014
"The first function to execute in the simple directive during view rendering will be the compile function." When would a person write an Angular directive with a compile function-- can you give an example? Can you give an explanation of when a link function would be used in preference to a directive with a watch and a controller, but no link function? Thanks!
gravatar Ernesto Chavez Friday, May 30, 2014
Excellent post, can you expand a bit as to what would go inside the controller function for a directive if we are doing the scope bindings, watches, events during the link function. Thanks
gravatar David Monday, June 2, 2014
@Mark Compile is useful for when you have a complex template you want to compile once and use later in link functions when the directives are instantiated. For most custom directives you won't need it, but if you are building up a crazy expression you can optimize your code by doing it in the compile phase. -David
gravatar Rehan Chawdry Tuesday, June 3, 2014
Excellent article. One area to expand more on might be the order of when compile, controller, prelink, and postlink come into play with nested directives. There is an especially important ordering process that happens that could be expanded on. Thanks again!
gravatar John Chappell Tuesday, June 3, 2014
Maybe a suggestion from this post could be a directive life cycle cheat sheet. Somebody should do that... I'm somebody! ;)
gravatar Dragos Tudorache Tuesday, June 3, 2014
@Rehan - this is a small bin that demonstrates the order of compilation within nested directives http://jsbin.com/xadap/1/edit
gravatar Francisc Friday, June 6, 2014
Which are the "couple special scenarios" when pre-linking is useful?
gravatar Scott Sunday, June 8, 2014
@Francisc: One example is NgModelController which uses pre to register itself with NgFormController, and post to do the usually event wiring, etc.
gravatar Aries Monday, June 9, 2014
Thanks for making it short and sweet. I break through my head to get things done with directive. This will be helpful for me in future.
gravatar PM Wednesday, June 18, 2014
Hello Scott, as you stated that after compilation controller function is first function where we can instantiate $scope and in link function we apply event handlers and manupulate data. my concern is that if we are doing everything within link function then why do we need controller just only to instantiate $scope? I have seen some directive example with doesn't contain crontroller function, it means controller calls internally and automatically?
gravatar Vinay Friday, July 11, 2014
Hello Scott, when markup is: <outer><inner></inner><outer>, order of execution is: 1. pre of outer 2. pre of inner 3. post of inner 4. post of outer when markup is <outer><inner ng-repeat="item in [1,2,3]" ></inner><outer>, order of execution is: 1. pre of outer 2. post of outer 3. pre of inner 4. post of inner Could you please explain me this behavior?
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!