Web Front-end10 minute read

AngularJS Tutorial: Demystifying Custom Directives

One of the most capable, extensible and popular front-end frameworks is AngularJS, and one of the most useful components of the AngularJS framework is something called a directive. In this article, the four functions that execute as a directive is created and applied to the DOM will be explored.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

One of the most capable, extensible and popular front-end frameworks is AngularJS, and one of the most useful components of the AngularJS framework is something called a directive. In this article, the four functions that execute as a directive is created and applied to the DOM will be explored.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Eric W. Greene
Verified Expert in Engineering
25 Years of Experience

Eric loves to write code in a high-paced and challenging environment with an emphasis on using best practices.

Share

With the rapid growth of JavaScript as a full stack language, more and more applications are utilizing frameworks that enable the web browser to handle more of the UI processing such as data binding, managing data views, transforming data and many other services. One of the most capable, extensible and popular frameworks is AngularJS, and one of the most useful components of the AngularJS framework is something called a directive. AngularJS provides many useful directives and, even more importantly, it provides a rich framework for creating custom directives.

What is a directive? To put it simply, directives are JavaScript functions that manipulate and add behaviors to HTML DOM elements. Directives can be very simplistic or extremely complicated. Therefore, getting a solid grasp on their many options and functions that manipulate them is critical.

In this tutorial, the four functions that execute as a directive is created and applied to the DOM will be explored and examples will be provided. This post assumes some familiarity with AngularJS and custom directives. If you’re newer to Angular, you might enjoy a tutorial on building your first AngularJS app.

The Four Functions of the AngularJS Directive Life Cycle

There are many options that can be configured and how those options are related to each other is important. Each directive undergoes something similar to a life cycle as AngularJS compiles and links the DOM. The directive lifecycle begins and ends within the AngularJS bootstrapping process, before the page is rendered. In a directive’s life cycle, there are four distinct functions that can execute if they are defined. Each enables the developer to control and customize the directive at different points of the life cycle.

The four functions are: compile, controller, pre-link and post-Link.

The compile function allows the directive to manipulate the DOM before it is compiled and linked thereby allowing it to add/remove/change directives, as well as, add/remove/change other DOM elements.

The controller function facilitates directive communication. Sibling and child directives can request the controller of their siblings and parents to communicate information.

The pre-link function allows for private $scope manipulation before the post-link process begins.

The post-link method is the primary workhorse method of the directive.

In the directive, post-compilation DOM manipulation takes place, event handlers are configured, and so are watches and other things. In the declaration of the directive, the four functions are defined like this.

  .directive("directiveName",function () {

    return {
      controller: function() {
        // controller code here...
      },
      compile: {
  
        // compile code here...

        return {

          pre: function() {
            // pre-link code here...
          },
      
          post: function() {
            // post-link code here...
          }
        };
      }
    }
  })

Commonly, not all of the functions are needed. In most circumstances, developers will simply create a controller and post-link function following the pattern below.

  .directive("directiveName",function () {

    return {

      controller: function() {
        // controller code here...
      },
  
      link: function() {
        // post-link code here...
      }
    }
  })

In this configuration, link refers to the post-link function.

Whether all or some of the functions are defined, their execution order is important, especially their execution relative to the rest of the AngularJS application.

AngularJS Directive Function Execution Relative to other Directives

Consider the following HTML snippet with the directives parentDir, childDir and grandChildDir applied to the HTML fragment.

<div parentDir>
  <div childDir>
    <div grandChildDir>
    </div>
  </div>
</div>

The execution order of the functions within a directive, and relative to other directives, is as follows:

  • Compile Phase
    • Compile Function: parentDir
    • Compile Function: childDir
    • Compile Function: grandChildDir
  • Controller & Pre-Link Phase
    • Controller Function: parentDir
    • Pre-Link Function: parentDir
    • Controller Function: childDir
    • Pre-Link Function: childDir
    • Controller Function: grandChildDir
    • Pre-Link Function: grandChildDir
  • Post-Link Phase
    • Post-Link Function: grandChildDir
    • Post-Link Function: childDir
    • Post-Link Function: parentDir

The AngularJS directive function tutorial - execution order relative to other directives.

AngularJS Directive Function Explanation: Deep Dive

The compilation phase occurs first. Essentially, the compile phase attaches event listeners to the DOM elements. For example, if a particular DOM element is bound to a $scope property, the event listener that allows it to be updated with the the value of the $scope property is applied to the DOM element. The process of compilation starts with the root DOM element from which the AngularJS application was bootstrapped and traverses down the branches of the DOM using a depth-first traversal, compiling a parent first then its children all the way down to the leaf nodes.

Once compilation is complete, directives can no longer be added or removed from the DOM (although there is way around this by directly using the compile service. The next phase is the calling of controllers and pre-link functions for all directives. When the controller is called, the $scope is available and can be used. The $element injected into the controller contains the compiled template but does not include the transcluded child content (transcluded content is the content between the start and end HTML tags on which the directive is applied). By definition, controllers in an MVC pattern simply pass the model to the view and define functions for handling events. Therefore, the controller of a directive should not modify the DOM of the directive for two reasons: it violates the purpose of the controller, and the transcluded child content has not been added to the DOM. So what does a controller do beyond modify the $scope? The controller allows for child directives to communicate with parent directives. The controller function itself should be thought of as a controller object that will be passed into the child directive’s post-link function if the child directive requests it. Therefore, the controller is typically used to facilitate directive communication by creating an object with properties and methods that can be used by its sibling and child directives. The parent directive cannot determine whether a child directive can require its controller, so it is best to limit code in this method to functions and properties that can safely be used by child directives.

After the controller function, the pre-link function executes. The pre-link function is mysterious to a lot of people. If you read much of the documentation on the Internet and in books, people write that this function is used only in rare circumstances and people will almost never need it. Those same explanations then fail to give an example of a situation where it could be used.

The pre-link function is really not complicated at all. First, if you review the AngularJS source code you will find an excellent example of the pre-link function: the directive ng-init uses it. Why? It’s simply a great method to execute private code involving the $scope; code that cannot be called by sibling and child directives. Unlike the controller function, the pre-link function is not passed into directives. Therefore, it can be used to execute code that modifies the $scope of its directive. The directive ng-init does exactly this. When the pre-link function for ng-init executes, it simply executes the JavaScript passed into the directive against the directive’s $scope. The result of the execution is available through the $scope’s prototypal inheritance to child directives during their controller, pre-link and post-link function executions but without giving access to those child directives to re-execute the code in the parent’s pre-link function. Also, the directive may need to execute other code not related to the $scope that it wishes to keep private.

Some experienced AngularJS developers would say this private code could still be placed in the controller and then not called by the child directives. That argument would hold true if the directive will only be used by the original developer who coded it but if the directive is going to be distributed and reused by other developers then encapsulating private code in the pre-link function could be very beneficial. Since developers never know how their directive will be re-used over the course of time, protecting private code from being executed by a child directive is a good approach to directive code encapsulation. I consider it to be a good practice to place directive communication public code in the controller function, and private code in the pre-link function. Like the controller, the pre-link should never do DOM manipulation nor execute a transclude function, since the content for child directives has not been linked yet.

For each directive, its controller and pre-link function executes before the controller and pre-link function of its child directives. Once the controller and pre-link phase for all directives is complete then AngularJS begins the linking phase, and executes the post-link functions for each directive. The linking phase runs opposite to the compile, controller and pre-link execution flows by starting with the leaf DOM nodes and working its way up to the root DOM node. The post-link DOM traversal follows a mostly depth-first path. As each child directive is linked, its post-link function is executed.

The post-link function is the function most commonly implemented in custom AngularJS directives. In this function, almost anything reasonable can be done. The DOM can be manipulated (for itself and child elements only), the $scope is available, the controller object for parent directives can be used, transclude functions can be run, etc. However, there are a few limitations. New directives cannot be added to the DOM because they will not be compiled. Additionally, all DOM manipulations must be done using DOM functions. Simply calling the html function on the DOM element and passing in new HTML will remove all of the event handlers added during the compile process. For example, these will not work as expected:

  element.html(element.html());

or

  element.html(element.html() + "<div>new content</div>");

The code will not cause the HTML to change, but reassigning the string version of the DOM elements will remove all of the event handlers created during the compile process. Typically, the post-link function is used to wire up event handlers, $watches and $observes.

Once all of the post-link functions are executed, the $scope is applied to the compiled and linked DOM structure, and the AngularJS page comes alive.

Directive Function Chart

Here is chart listing the purpose of each function, what is available when it executes, and best practices on how to use each function appropriately.

Execution
Order
Directive
Function
DOMTransclude$scopeCallable
by Child
1compileDOM has not been compiled but template has been loaded into the DOM element content area. Directives can be added and removed. DOM can be manipulated with both DOM functions and HTML string replacement.Transclude function is available but is deprecated and should not be called.Not available.Function cannot be called by child elements.
2controllerCompiled DOM element is available but should not be modified. Transcluded child content has not been added to the DOM element. No DOM changes should occur because this is a controller and transcluded child content has not been linked in yet.Transclude function is available but should not be called. $scope is available and can be used. Function parameters are injected using the $injector service.Function is passed into child directive linking functions and is callable by them.
3pre-linkCompiled DOM element is available but should not be modified because child directive DOM elements have not been linked in yet.Transclude function is available but should not be called. $scope is available and can be modified.Function is not callable by child directives. But may call the controllers of parent directives.
4post-linkCompiled DOM element and child directive DOM elements are available. DOM can be modified with DOM functions only (no HTML replacement) and only content that does not require compilation can be added. No adding/removing of directives is allowed.Transclude function is available and may be called. $scope is available and may be used.Not callable by directive children but may call the controller of parent directives.

Summary

In this tutorial on AngularJS directives, we have learned about the purpose, execution order and overall capabilities and uses for each of the four directive functions: compile, controller, pre-link and post-link. Of the four functions, controller and post-link are the most commonly used but for more complex directives that need to have greater control of the DOM or need a private scope execution environment the compile and pre-link functions can be utilized.

Hire a Toptal expert on this topic.
Hire Now
Eric W. Greene

Eric W. Greene

Verified Expert in Engineering
25 Years of Experience

Amherst, VA, United States

Member since December 13, 2013

About the author

Eric loves to write code in a high-paced and challenging environment with an emphasis on using best practices.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Join the Toptal® community.