Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)
<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>
<body>
    <div log='some-div'></div>
</body>
myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});
some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

@Anant's plunker disappeared so here's a new one: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Open the JS console to see the log statements

@Luckylooke Your plunk has got no children with directive under ng-repeat (ie, what's being repeated is a template with a directive. If it would, you'd see that their compile is only called after the link of ng-repeat.

@lzhaki The flowchart looks nice. Mind to share the charting tool's name? :)

@merlin I've used OmniGraffle (but could have used illustrator or inkscape - other than speed, there's nothing OmniGraffle does better than other charting tools as far as this illustration is concerned).

Based on the following plunk, consider the following HTML markup:

DOM instances are often simply the result of a source template being rendered to the DOM, but they may be created by ng-repeat, or introduced on the fly.

In this phase, Angular calls controller, pre-link, iterates children, and call post-link on all directives, like so:

It is perhaps important to mention that at this stage, the templates the compile function gets are the source templates (not instance template).

Note: The following does not apply to directives that render their children in their link function. Quite a few Angular directives do so (like ngIf, ngRepeat, or any directive with transclude). These directives will natively have their link function called before their child directives compile is called.

The original HTML markup is often made of nested elements, each with its own directive. Like in the following markup (see plunk):

WHY this is not true when ng-repeat is used for children directives??? See plunk:plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview

We can distinguish two phases here - the compile phase and the link phase.

We can see that compile is executed first, then controller, then pre-link and last is post-link.

When the DOM is loaded Angular starts the compile phase, where it traverses the markup top-down, and calls compile on all directives. Graphically, we could express it like so:

Whenever a new instance of an element with a directive is rendered to the DOM, the link phase starts.

With the following directive declaration:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>
<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>
compile
compile: function compile( tElement, tAttributes ) { ... }
controller
controller: function( $scope, $element, $attrs, $transclude ) { ... }
function postLink( scope, element, attributes, controller ) { ... }
function preLink( scope, element, attributes, controller ) { ... }
myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});
pre-link

Entering the link phase, the link function returned via $compile is now provided with a scope.

First, the link function create a child scope (scope: true) or an isolated scope (scope: {...}) if requested.

Following the .preLink call, the link function will traverse each child element - calling the correct link function and attaching to it the current scope (which serves as the parent scope for child elements).

Following the call to the directive's .compile, Angular will traverse all child elements, including those that may have just been introduced by the directive (the template elements, for instance).

In our case, three instances of the source template above will be created (by ng-repeat). Thus, the following sequence will execute three times, once per instance.

Notice that the transcluded content is not re-inserted at this point.

Often the parameters are prefixed with t to signify the elements and attributes provided are those of the source template, rather than that of the instance.

Perhaps worth noticing that once a directive's .postLink function is called, the link process of all its children elements has completed, including all the children's .postLink functions.

Prior to the call to compile transcluded content (if any) is removed, and the template is applied to the markup. Thus, the element provided to the compile function will look like so:

The controller is then executed, provided with the scope of the instance element.

The post-link API is similar to that of the pre-link function:

The various directive functions are executed from within two other angular functions called $compile (where the directive's compile is executed) and an internal function called nodeLinkFn (where the directive's controller, preLink and postLink are executed). Various things happen within the angular function before and after the directive functions are called. Perhaps most notably is the child recursion. The following simplified illustration shows key steps within the compile and link phases:

This means that by the time .postLink is called, the children are 'live' are ready. This includes:

To demonstrate the these steps, let's use the following HTML markup:

Virtually nothing happens between the call to the directive's .controller and the .preLink function. Angular still provide recommendation as to how each should be used.

With the following directive:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)
<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>
<body>
    <div log='some-div'></div>
</body>
myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});
some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

@Anant's plunker disappeared so here's a new one: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Open the JS console to see the log statements

@Luckylooke Your plunk has got no children with directive under ng-repeat (ie, what's being repeated is a template with a directive. If it would, you'd see that their compile is only called after the link of ng-repeat.

@lzhaki The flowchart looks nice. Mind to share the charting tool's name? :)

@merlin I've used OmniGraffle (but could have used illustrator or inkscape - other than speed, there's nothing OmniGraffle does better than other charting tools as far as this illustration is concerned).

Based on the following plunk, consider the following HTML markup:

DOM instances are often simply the result of a source template being rendered to the DOM, but they may be created by ng-repeat, or introduced on the fly.

In this phase, Angular calls controller, pre-link, iterates children, and call post-link on all directives, like so:

It is perhaps important to mention that at this stage, the templates the compile function gets are the source templates (not instance template).

Note: The following does not apply to directives that render their children in their link function. Quite a few Angular directives do so (like ngIf, ngRepeat, or any directive with transclude). These directives will natively have their link function called before their child directives compile is called.

The original HTML markup is often made of nested elements, each with its own directive. Like in the following markup (see plunk):

WHY this is not true when ng-repeat is used for children directives??? See plunk:plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview

We can distinguish two phases here - the compile phase and the link phase.

We can see that compile is executed first, then controller, then pre-link and last is post-link.

When the DOM is loaded Angular starts the compile phase, where it traverses the markup top-down, and calls compile on all directives. Graphically, we could express it like so:

Whenever a new instance of an element with a directive is rendered to the DOM, the link phase starts.

With the following directive declaration:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>
<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>
compile
compile: function compile( tElement, tAttributes ) { ... }
controller
controller: function( $scope, $element, $attrs, $transclude ) { ... }
function postLink( scope, element, attributes, controller ) { ... }
function preLink( scope, element, attributes, controller ) { ... }
myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});
pre-link

Entering the link phase, the link function returned via $compile is now provided with a scope.

First, the link function create a child scope (scope: true) or an isolated scope (scope: {...}) if requested.

Following the .preLink call, the link function will traverse each child element - calling the correct link function and attaching to it the current scope (which serves as the parent scope for child elements).

Following the call to the directive's .compile, Angular will traverse all child elements, including those that may have just been introduced by the directive (the template elements, for instance).

In our case, three instances of the source template above will be created (by ng-repeat). Thus, the following sequence will execute three times, once per instance.

Notice that the transcluded content is not re-inserted at this point.

Often the parameters are prefixed with t to signify the elements and attributes provided are those of the source template, rather than that of the instance.

Perhaps worth noticing that once a directive's .postLink function is called, the link process of all its children elements has completed, including all the children's .postLink functions.

Prior to the call to compile transcluded content (if any) is removed, and the template is applied to the markup. Thus, the element provided to the compile function will look like so:

The controller is then executed, provided with the scope of the instance element.

The post-link API is similar to that of the pre-link function:

The various directive functions are executed from within two other angular functions called $compile (where the directive's compile is executed) and an internal function called nodeLinkFn (where the directive's controller, preLink and postLink are executed). Various things happen within the angular function before and after the directive functions are called. Perhaps most notably is the child recursion. The following simplified illustration shows key steps within the compile and link phases:

This means that by the time .postLink is called, the children are 'live' are ready. This includes:

To demonstrate the these steps, let's use the following HTML markup:

Virtually nothing happens between the call to the directive's .controller and the .preLink function. Angular still provide recommendation as to how each should be used.

With the following directive:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


<my-directive>{{i}}</my-directive>
<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>
  • Instance template - the actual markup to be rendered to the DOM. If cloning is involved, each instance will be a clone.
  • Source template - the markup to be cloned, if needed. If cloned, this markup will not be rendered to the DOM.

Angular terminology is a bit inconsistent, but it still distinguishes between two types of markups:

But as it is wrapped within an ng-repeat directive, this source template will be cloned (3 times in our case). These clones are instance template, each will appear in the DOM and be bound to the relevant scope.

The fact that Angular allows DOM manipulation means that the input markup into the compilation process sometimes differ from the output. Particularly, some input markup may be cloned a few times (like with ng-repeat) before being rendered to the DOM.

The following markup demonstrates this:

which serves as the source template.

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

If no compile function is needed, one can skip its declaration altogether and provide the post-link function under the link property of the directive's configuration object:

If pre-link isn't necessary, the compile function can simply return the post-link function instead of a definition object, like so:

In any of the examples above, one can simply remove the controller function if not needed. So for instance, if only post-link function is needed, one can use:

Notice that compile returns an object containing both the pre-link and post-link functions; in Angular lingo we say the compile function returns a template function.

Sometimes, one wishes to add a compile method, after the (post) link method was defined. For this, one can use:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


<my-directive>{{i}}</my-directive>
<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>
  • Instance template - the actual markup to be rendered to the DOM. If cloning is involved, each instance will be a clone.
  • Source template - the markup to be cloned, if needed. If cloned, this markup will not be rendered to the DOM.

Angular terminology is a bit inconsistent, but it still distinguishes between two types of markups:

But as it is wrapped within an ng-repeat directive, this source template will be cloned (3 times in our case). These clones are instance template, each will appear in the DOM and be bound to the relevant scope.

The fact that Angular allows DOM manipulation means that the input markup into the compilation process sometimes differ from the output. Particularly, some input markup may be cloned a few times (like with ng-repeat) before being rendered to the DOM.

The following markup demonstrates this:

which serves as the source template.

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


In case anyone is using the link function (without pre-link or post-link), it's good to know that it's equivalent to the post-link.

When the post-link function is called, all previous steps have taken place - binding, transclusion, etc.

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

If no compile function is needed, one can skip its declaration altogether and provide the post-link function under the link property of the directive's configuration object:

If pre-link isn't necessary, the compile function can simply return the post-link function instead of a definition object, like so:

In any of the examples above, one can simply remove the controller function if not needed. So for instance, if only post-link function is needed, one can use:

Notice that compile returns an object containing both the pre-link and post-link functions; in Angular lingo we say the compile function returns a template function.

Sometimes, one wishes to add a compile method, after the (post) link method was defined. For this, one can use:

Note
Rectangle 27 1

angularjs Angular directives when and how to use compile, controller, pre link and post link?


In case anyone is using the link function (without pre-link or post-link), it's good to know that it's equivalent to the post-link.

When the post-link function is called, all previous steps have taken place - binding, transclusion, etc.

Note