Angular Directives Like You Designed Them

Confused about Angular Directives? Well, don't be. Lean them like you designed it!

Written by 42ID on April 14 2015

Hey guys! Welcome to tutorials by 42ID. In this article we are going to take a look at AngularJS Directives. Directives are one of the most powerful features of Angular JS and also an area where there is a lot of confusion. We'll cut through all the confusion by talking about how directives work and their underlying design principles. Once we are done, you'll understand directives as if you designed the implementation for Angular yourself!

With that, lets start with what we know about angular directives. We know that Angular directives are a way to extend the HTML DOM. Directives do this by allowing you to manipulate the DOM (i.e. add/remove DOM elements, change CSS class, or add/remove HTML attributes). However, their usefulness does not stop there. In fact, they allow you to manipulate the DOM based on some application data. In other words, directives link the DOM manipulation to application data. That's the key part here!

Let's start with a basic application that pulls the top 20 news from Hacker News and displays it on our view. You can follow along with this plunker. The application has a controller ( post.controller.js) that has a view associated with it (index.html). The controller pulls information from Hacker News (via dataService.js) and returns this information to the view.

Let us focus on this section where we have the ng-repeat in the index.html.

<li ng-repeat="post in vm.posts">{{post.by}} -- {{post.title}}</li>

ng-repeat is an inbuilt angular directive that allows us to repeat a template a number of times. For each news article that we pull back from hacker news, this directive is going to repeat the template defined between the <li> tag.

Lets start simple. Lets begin by creating a custom directive that encapsulates the creation of this template. As we progress, we'll see how we can create our own custom ng-repeat directive.

We begin by adding a new file called postItem.directive.js. This will contain our directive definition. Directives are defined on Angular Module. So we get the reference to our angular module and invoke the directive API on it. The API returns an object called as the Directive Definition Object. This object contains the information needed by Angular to configure and render our directive. One of the most basic parameter is the 'templateUrl'. This parameter points to a template file that contains the DOM that will be added. In our case, we have indicated that the template will come from the postItem.tmpl.html file. Next we replace the section within <li> tag with our custom directive <post-item>. Try it on Plunker.

OK, so what have we accomplished here?

We have extracted and encapsulated certain DOM manipulations into a more concise form - a form that could potentially be reused within the application in other places. Before we proceed further, lets pause and reflect on few things here. As developers, if we were to implement a mechanism to perform exactly what we accomplished using directives, how would we go about doing that? In other words, how would you go about developing your own implementation of what angular directives do?

To answer that, lets take a look at this sample DOM

<html ng-app>
<head>
</head>
<body ng-controller>
  <div ng-show ng-class>
  </div>
</body>
</html>


In order to build a DOM manipulation library, we would:

1) Have to scan the entire DOM, starting at a node and traversing its children and siblings and identify all nodes that have a directive on them. For each one of the directives, we have to look up an implementation for that directive's behavior.

2) If we can also associate application data along with the behavior, we can then achieve some sort of binding with the application data when we apply the behaviour.

And this is exactly what Angular does for its directives.

Its implementation is broken down into 2 phases:

1) Compiling phase - that scans the entire DOM structure, identifies the directives and applies the DOM manipulation.
2) Linking phase - associates application data in form of a scope object to the DOM elements that we are manipulating.

The Angular directive compiler traverses the DOM tree and at each node, collects all the directives that are present at that node. It then calls the compile function defined in the Directive Definition Object for each one these directives. This function in the Directive Definition Object is the hook that Angular provides for us to define our custom behavior. The compiler automatically sends in the reference to the element as well the attributes defined on the element into this function. The attributes parameter passed in is an Angular object that wraps the attributes on the DOM. With these 2 arguments, now available to us, we can go ahead and define any kind of DOM manipulation at this node.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        compile: function(element, attr){
            // DOM manipulation here using javascript/jquery since we have access to the element
        }
    };
});

That takes care of the compiling phase. What about the link phase? Link phase is where Angular attaches application data. And how does Angular hook into the link phase? It does that by running a link function at the end of the compile phase. Angular expects the link function to be defined and returned from the compile function.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        compile: function(element, attr){
            // DOM manipulation here using javascript/jquery since we have access to the element
            return linkFunction;
        }
    };
});

So, the directive compiler first runs all the compile functions and these return a link function that angular collects and runs one after the other after it has run all the compile functions.

Now, lets take a look at this link function that Angular automatically calls for us at the end of the compile phase.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        compile: function(element, attr){
            // DOM manipulation here using javascript/jquery since we have access to the element
                return function(scope, element, attr){
                    // LINK function
                };
        }
    };
});

The link function is called by Angular with 3 parameters - the scope that is currently available to Angular, the element and attributes that was used to called the compile function.

Looking at this function, we see that we again have access to the element and attributes of the node where the directive was defined. Since we have access to the element, we could very well perform any DOM manipulation in here as well. With the added benefit that we now also have access to the application data through the scope parameter.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        compile: function(element, attr){
            // DOM manipulation here using javascript/jquery since we have access to the element
                return function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                };
        }
    };
});

In many cases, this would server our purpose. In order to facilitate this, Angular provides a shortcut method to define the above API. So, in cases where you dont have to do any manipulation in the compile phase, but can defer your manipulations to the link phase, you can rewrite this API by defining a link function in the Directive Definition Object:


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
        }
    };
});

And thats all there is to Angular directives! Let's go ahead and define an implementation for ng-show. Assuming we have jquery library defined, we can do something like this for our implementaion of ng-show.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    if(scope.showElement){
                        element.show(); //jquery
                    }else {
                        element.hide();
                    }
        }
    };
});

So we are now in a position to manipulate the DOM based on some application data - in this case, the application data is scope.showElement.

Now this establishes a one time binding with the application data. Now, thats not very useful by itself, because , well its just a one time binding. But remember that we have scope being passed in as a parameter. So we can use the $watch method on the scope to watch for changes and react to them.

So, let's refactor this to do that.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.showElement, function(value){
                        if(value){
                            element.show(); //jquery
                        }else {
                            element.hide();
                        }
                    });
        }
    };
});

Now, we have a continuous link to the application data. Whenever the application data changes, our link function will react and reflect the change accordingly.

Directives are powerful because of the interplay between the link function and the scope object. Angular allows us to customize the scope object that is passed in. We'll look at all the different ways Angular allows us to do that in the next article.

So stay tuned!! And feel free to get in touch with us with any feedback or question. You can reach us by sending us an message on Twitter @TheLatestBrian or @YanamegaInfo

Other articles you may be interested in:

Have a topic you'll like us to cover? Send us a message on Twitter!