New Toronto Group
 - New Toronto Group

Understanding Priorities in AngularJS Directive Definition Objects

| Posted on December 15th, 2014 by Andrew Smith


Learning and understanding the different stages Angular goes through when processing templates – the compile phase, the pre-linking phase, and the post-linking phase – and what order they fire in, is an important step to take when moving beyond a beginner’s understanding of Angular.

For that reason it’s a topic that has been explained often. My favourite explanation, for it’s shear clarity, is the one Jurgen Van de Moere gives on his blog. However, what doesn’t get explained so often (or at least with as much clarity) is the optional priority property on directive definition objects. My goal in this post is to illustrate in a similar fashion to Moere’s post, how priorities work. 

Processeing Nested Directives 

There is no point in repeating fully Moere’s post because you can read it on his blog but I do want to show the general outline of his explanation because it’s what I used when figuring out priorities.

Here is the HTML directly from his blog post:

<level-one>     
  <level-two>       
    <level-three>           
      Hello {{name}}                
   </level-three>   
  </level-two>
</level-one> 

And here is the JavaScript:

var app = angular.module('plunker', []);  

function createDirective(name) {     
  return function () {     
    return {       
      restrict: 'E',       
      compile: function (tElem, tAttrs) {
        console.log(name + ': compile');
        return {
          pre: function (scope, iElem, iAttrs) {
            console.log(name + ': pre link');
          },
          post: function (scope, iElem, iAttrs) {
            console.log(name + ': post link');
          }
        }
      }
    }
  }
}

app.directive('levelOne', createDirective('levelOne'));   
app.directive('levelTwo', createDirective('levelTwo'));   
app.directive('levelThree', createDirective('levelThree'));

And then he explains: “The goal is simple: let AngularJS process three nested directives where each directive has its own compilepre-link and post-link function that logs a line to the console so we can identify them.”

Essentially, we want to see what order each of these functions is getting called in, thereby identifying the order of the compile, pre-link, and post-link phases.

Finally, if you run this example and look at the console, you’ll get the following output:

levelOne: compile
levelTwo: compile
levelThree: compile
levelOne: pre link
levelTwo: pre link
levelThree: pre link
levelThree: post link
levelTwo: post link
levelOne: post link

What’s going on is that Angular traverses the DOM from top to bottom when compiling and pre-linking but then traverses the DOM in the opposite order in the post-linking phase. This ensure that all child elements of a given parent element have been completely settled before the post-linking of the parent element occurs and you can therefore be confident that it is safe to add any logic that may affect child elements.

There’s more to say about these different phases but understanding this aspect of how Angular processes directives is a vital key in determining what actions to take in the different phases.

Processing Multiple Directives on a Single DOM Element

Now, what if instead of nesting directives, you have several directives on one element. What order do the compile, pre-link, and post-link functions fire in? This is where priority comes in.

The Angular docs say: 

When there are multiple directives defined on a single DOM element, sometimes it is necessary to specify the order in which the directives are applied. The priority is used to sort the directives before their compile functions get called. Priority is defined as a number. Directives with greater numerical priority are compiled first. Pre-link functions are also run in priority order, but post-link functions are run in reverse order. The order of directives with the same priority is undefined. The default priority is 0.

Given the demonstration of nested directives that was just given, you should have a pretty good idea of what to expect now but if you’re like me and things don’t really sink in until you see a demonstration and play with it yourself, then continue on and we’ll modify the above illustration to make this explicit.

The changes we are going to make are minor. Instead of nesting directives, we’ll declare our three directives on a single element.

<div level-one level-two level-three>

And now since we’re using them as attributes we need to be sure to set:

restrict: 'A’

Now when we look at our console we get this:

levelOne: compile
levelThree: compile
levelTwo: compile
levelOne: pre link
levelThree: pre link
levelTwo: pre link
levelTwo: post link
levelThree: post link
levelOne: post link

What’s going on here? Well it turns out that if multiple directives on the same element have the same priority, in this case 0 because 0 is the default if no priority is specified, they are processed in alphabetical order, NOT the order that they are declared in on the directive. Try it out. Change the names of the directives and change the order that they are specified on the element. As long as they all have the same priority, they will be processed in alphabetical order.

In many cases it won’t be a problem that they are being processed alphabetically. However it might become a problem if some of your directives are dependent on other directives on the same element. In that case you can specify the priority of a directive in order to control which order they get processed in.

Let’s change our example again. We’ll use the same HTML as before but now we’ll specify the priority.

var app = angular.module('myApp', []);
 
    function createDirective(name, priority) {    
      return function () {    
        return {      
          priority: priority,
          restrict: 'A',      
          compile: function (tElem, tAttrs) {
            console.log(name + ': compile');
            return {
              pre: function (scope, iElem, iAttrs) {
                console.log(name + ': pre link');
              },
              post: function (scope, iElem, iAttrs) {
                console.log(name + ': post link');
              }
            }
          }
        }
      }
    }
 
  app.directive('levelOne', createDirective('levelOne', 1));  
  app.directive('levelTwo', createDirective('levelTwo', 2));  
  app.directive('levelThree', createDirective('levelThree', 3));

Looking at the console now we see that they are compiled and pre-linked starting with the directive of the highest priority (meaning the highest number) and then as before, the post-linking occurs in reverse order.

levelThree: compile
levelTwo: compile
levelOne: compile
levelThree: pre link
levelTwo: pre link
levelOne: pre link
levelOne: post link
levelTwo: post link
levelThree: post link

This caught me off guard the first time I saw it, even though I already knew that nested directives would post-link in reverse order, which is why I wanted to illustrate it clearly here. If you switch the priority around then you’ll get output equivalent to what it was when these directives were nested.

For example:

app.directive('levelOne', createDirective('levelOne', 3));  
app.directive('levelTwo', createDirective('levelTwo', 2));  
app.directive('levelThree', createDirective('levelThree', 1));

Produces:

levelOne: compile
levelTwo: compile
levelThree: compile
levelOne: pre link
levelTwo: pre link
levelThree: pre link
levelThree: post link
levelTwo: post link
levelOne: post link

Finally, one more thing to keep in mind. Most core Angular directives have a priority of 0 which means if you want to process your directive after a core angular directive (or any other directive with a priority of 0) then you need to supply a negative number. For example:

app.directive('levelOne', createDirective('levelOne', 0));  
app.directive('levelTwo', createDirective('levelTwo', 1));  
app.directive('levelThree', createDirective('levelThree', -1));

Produces this output:

levelTwo: compile
levelOne: compile
levelThree: compile
levelTwp: pre link
levelOne: pre link
levelThree: pre link
levelThree: post link
levelOne: post link
levelTwo: post link

And that pretty much wraps up priority. It’s fairly straightforward but like most concepts that seem straightforward, they can be a little unintuitive when first encountered and it’s best to tinker with some examples to really get a handle on predicting the output you’ll get.

Posted in AngularJS  | Comments (8)

Comments (8)

  1. Yazan Rawashdeh:
    Mar 04, 2015 at 10:35 AM

    Awesome Explanation
    Thank you so much , this really helped :D

  2. Petar:
    Apr 29, 2015 at 01:29 PM

    Thanks man. Clear explanation. Well done :)

  3. elom:
    Jun 10, 2015 at 09:26 AM

    Thanks a lot, it's very difficult to understand such things in angular.

  4. Shibbir:
    Aug 31, 2015 at 08:40 AM

    Very clear explanation. Thank you.

  5. Steven:
    Jan 13, 2016 at 02:52 AM

    Nice article. Thanks!

  6. Kapil Seth:
    Aug 11, 2016 at 02:17 AM

    Good way of writing the articles, i loved it .. Many Thanks :)

  7. Jeevan:
    Sep 02, 2016 at 06:17 AM

    excellent way of explaining the life cycle

  8. Di ying:
    Nov 20, 2016 at 10:47 AM

    Very nice post.I read each word through!


Add a Comment





To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey

Allowed tags: <b><i><br>Add a new comment: