CBT Nuggets

AngularJS In-Depth: Custom Directives

by Raju Woodward
AngularJS In-Depth: Custom Directives picture: A
Follow us
Published on August 12, 2014

Editor's Note: CBT Nuggets trainer Ben Finkel recently completed his "AngularJS" training course. He wrote the following blog post to address implementing custom directives in more depth. 

AngularJS custom directive compilation and linking

Recently, I've seen some confusion surrounding AngularJS' implementation of custom directives. Specifically, the compilation and linking steps and how they interact on a more complex Angular app. Having spent quite a bit of time researching and learning about those exact processes in order to record my AngularJS training course videos, I thought I would try to share some of the insight I've gained.

It can take a little bit of effort to get your head truly wrapped around the entire process, but ultimately I don't think it's especially complicated.  AngularJS has a very straightforward approach to the whole matter of "compiling" your HTML template, and it's easy to demonstrate.

$compile

Before we get into concrete examples, let's take a look at the AngularJS $compile() operation.  Compiling is the operation that AngularJS uses to turn your marked-up HTML template into a functioning HTML and Javascript page. Before your page is compiled it can't be rendered properly by the browser. Why not?  Because it's covered in AngularJS specific directives such as "ng-repeat."  Your browser doesn't know what to do with "ng-repeat" so your HTML template is not renderable HTML yet. It needs to be processed by AngularJS, which is what compiling refers to.

When AngularJS compiles your page (compile is provided by the $compile() function) it walks down your DOM tree and looks for directives. It's trying to match each element and attribute it finds to the directive functions that have been registered in the app module. When it finds a match, it adds it to a short list of all the directives that are associated with the given DOM element it's currently searching for. Once all of the directives on that element are found, the custom directives are sorted by their priority and then each directive's compile function (if they have them) are executed one by one.

Here are two directives, which would be sorted by priority and then have their compile functions executed once AngularJS was done scanning the <p> element:

&lt;p ng-controller="Ctrl" my-customdirective&gt;…

A couple of important notes:

1.  The DOM is walked with a traversal process known as " depth-first pre-order."  Basically, this means Angular walks down each branch as deeply as it can before moving onto the next branch.  Wikipedia has a good write up about this and other tree traversal methods here:

https://en.wikipedia.org/wiki/Tree_traversal

[Fig 1 – Depth-first pre-order tree traversal. This is how AngularJS walks the DOM: FBADCEGIH]

2.  Each directive's compile function, regardless of what it does, returns a function called a linking function. Actually, it may return two linking functions, both a pre-link and post-link (more on this in a bit). The linking functions are not executed now. They are simply added to a list of linking functions for the entire DOM, and the walk continues.

3.  It's not necessary for a custom directive to have a compile function. In fact, it's fairly rare (for reasons we identify below). However, directives will always return a linking function and that function is added to the linking function list, regardless of whether or not they implement a compile function.

It's only after the entire DOM has been walked, all directives identified, their compile functions executed, and linking functions put into the list that the linking functions are then executed.  So, the compile operation can be thought of as a two stage process: Compile and Link.

[Fig 2 – The two main steps in the Angular $compile() operation]

Step 1: The compile executions and link list building happen as Angular walks down the tree at the end of each element. This means that all of the compile functions are executed before any of the linking functions.  It also means that the linking functions are loaded into the list in a very specific order. Keep both of these things in mind as we continue.

Custom Directives

When you build custom directives, you may recall that the first step is to register the directive function with your app module using the .directive() method. This method takes two parameters:

The name of the directive used by Angular during the initial DOM walk to match the custom items in the DOM to their respective directives.

The directive factory function which returns either a function to be used as a linking function, or an object known as a directive definition object.

myApp.directive("myCustomdirective", function(){
  return fnLink(){…};
});

AngularJS In-Depth: Custom Directives (Snippet) (fr migration)The actual factory function should do very little aside from producing linking functions.  At most, it might do pre-work to setup values for the directive definition object. None of the actual functionality of the directive should be handled there. In the simplest use-case shown above, the factory simply returns one linking function, which is known as a "post" linking function (more on that later).

Take a look at this code sample. Notice the alert on each factory function so you can see what order those factories are executed in. First, the top-level directive, my-direct1, is fired. Then all of its children before angular moves onto my-direct2, and its children.

https://plnkr.co/edit/ni4VN0mkc9NfdxlAd000

This second code sample below adds in an alert on each linking function. Take note of the following when it runs:

1. All of the factory functions fire before any of the linking functions (just like I told you they would!).

2. The order of the linking function executions. It's no longer in "pre order" traversal, but rather a depth-first "post order" traversal. This is important because it means that when a given directive's link function is called, you can be sure its children in the DOM have already had their compiling and linking performed and any DOM manipulation to be done on those children is complete.

https://plnkr.co/edit/MwiX82yzRmfFYfce10uK

[Fig 3 – Depth-first post-order tree traversal. This is the order in which the post linking functions execute: ACEDBHIGF]

Directive Definition Object

myApp.directive("myCustomdirective", function(){
  return {
    priority:n,
    link:fnLink(){}
  }
});

When a directive definition object is used, the syntax is not much more complicated. Here, an object is returned, and one of the object's properties is called "link" which simply provides the linking function like our more simple example above. The directive definition object also can have many other properties set (I used "priority" as an example, but the AngularJS documentation shows off many, many more).

So why use a directive definition object versus a straight linking function? In many cases it doesn't matter.  For most simple DOM linking operations, the link function is just fine. Angular does recommend using a directive definition object, but even just defining that object with nothing but a link function is fine. When you need to make more complex custom directives, however, you'll want to dig into the cool parameters of the directive definition object such as templateUrl, controller, or compile.

Using the compile option

Let's talk a little bit about that compile option I just mentioned.  The compile property of the directive definition object is a function whose return value is the linking function. This means that if you choose to use a compile function, you do not use the link property of your directive definition object.  They are mutually exclusive, so only use one or the other.

myApp.directive("myCustomdirective", function(){
  return {
    priority:n,
    compile:fnCompile(){
      return fnLink(){};
    }
  };
});

AngularJS In-Depth: Custom Directives (Snippet) (fr migration)Unlike the linking function, the compile function is fired off during the first stage of the AngularJS compile operation when it's originally walking the DOM. It's executed after the factory function but before the next DOM element is processed as seen in this example below.

https://plnkr.co/edit/34hOsR0zIOJ5AYwY6FtI?p=info

Note how each compile function is called immediately after its factory function, but the link functions are still saved for the second stage.

The compile function is primarily present to handle the task of transforming the DOM.  The easiest example of this in action is the ngRepeat directive.  ngRepeat repeats a portion of the DOM for some arbitrarily defined number "n."  In order to make this work, the initial state of the repeated element is stored in memory by the compile function and then written out during each iteration's linking process.

The compile function also offers one more feature that's worth discussing in detail:  pre-linking.

Pre- and Post- linking functions

Every example we've used so far in this post has referred to a linking function without indicating whether it was pre- or post-. This is because the linking functions are, by default, always post-linking functions. Unless a very specific configuration of the compile function is used, you can safely assume that the link function will run as the post.

That configuration is shown below, and very simply, the compile function returns an object with two properties instead of the linking function directly.

myApp.directive("myCustomdirective", function(){
 return {
   priority:n, 
    compile:fnCompile(){
      return {
        pre:fnPreLink(){},
        post:fnPostLink(){}
      };
    }
  };
});

AngularJS In-Depth: Custom Directives (Snippet) (fr migration)Those two properties are named 'pre' and 'post' and unsurprisingly they define the pre- and post- link functions. Here is an example of the pre-link function being used:

https://plnkr.co/edit/mO1M5MgUK2gUlOEDiMpl?p=info

The primary difference with the pre-link function is when they fire. Notice how, as before, all of the factory and compile functions execute before any of the link functions.  The order of the pre- and post- link functions is different though! The pre-link functions fire in "pre-order" traversal while the post-link functions are in "post-order."  So the final order of execution in this sample is:

1 Factory
1 Compile
1a Factory
1a Compile
1b Factory
1b Compile
2 Factory
2 Compile
2a Factory
2a Compile

…then…

1 pre-link
1a pre-link
1a post-link
1b pre-link
1b post-link
1 post-link
2 pre-link
2a pre-link
2a post-link
2 post-link

AngularJS In-Depth: Custom Directives (Snippet) (fr migration)The big implication here is that an element's children have not yet been linked, when the pre-link function on that element fires. As the AngularJS documentation says: It is not safe to do DOM manipulation on the children of an element during its pre-link phase. This is because those children may have their own directives with their own linking operations that might depend on the structure of the DOM as it was written by the developer. Also, notice it is not strictly true that the post-links execute after all of the pre-links.  The structure of the DOM and directives will highly influence that order (see how the 1a post runs prior to the 1b pre).

The upshot of all of this

For us as developers, usually nothing.  Any of the first three examples above will produce the same thing. Returning just a link function, returning a directive definition object with the link property defined, or returning a directive definition object with the compile property returning just one function ALL produce just a single post-link function for the directive. They are truly interchangeable.

Even going so far as to define the compile property is going to be too much in nine out of ten cases. It's only in very complex scenarios that you would need to define a compile function and utilize the 'pre' property to define a pre-linking function.

Hopefully though, this post helps you understand how and when you should use those properties, if the need arises. AngularJS is great at simplifying complex operations like this, while still exposing the complexity to be used when it's needed.

Thanks for reading — and I hope you found this informative!


Download

By submitting this form you agree to receive marketing emails from CBT Nuggets and that you have read, understood and are able to consent to our privacy policy.


Don't miss out!Get great content
delivered to your inbox.

By submitting this form you agree to receive marketing emails from CBT Nuggets and that you have read, understood and are able to consent to our privacy policy.

Recommended Articles

Get CBT Nuggets IT training news and resources

I have read and understood the privacy policy and am able to consent to it.

© 2024 CBT Nuggets. All rights reserved.Terms | Privacy Policy | Accessibility | Sitemap | 2850 Crescent Avenue, Eugene, OR 97408 | 541-284-5522