Directives in AngularJS are attributes of HTML elements that AngularJS provides us to augment the capabilities of HTML elements. AngularJS provides many built-in directives e.g. ng-app, ng-controller, ng-repeat, ng-model, ng-bind and so on. So by using ng-app on HTML tag we mark the beginning of an AngularJS app. AngularJS also gives us the feature of writing our own custom directives to extend the capability of an HTML element according to our needs. We can extend the abilities of HTML’s template to do anything that we can imagine.

Directives help us to make reusable components that can be used throughout an angular application.

Building Custom Directives:

Capture.PNG

Basically a directive returns a definition object with a bunch of key and value pairs. These key-value pairs decide the behavior of a directive. Well syntax may seem dangerous to you but it is not because most keys are optional and some are mutually exclusive. Let’s try to understand the meaning of these key-value pairs in real life.

Restrict:

Used to specify how a directive is going to be used in DOM. The directive can be used as an attribute (A), element (E), class(C) or comment (M). These options can be used alone or in combinations. Optional means if you do not specify the option then the default value is attribute.

Example:

  • As attribute: <[div custom-directive ]/>
  • As element: <[custom-directive]></custom-directive>
  • As class: <[div class="custom-directive"]/>
  • As comment: <[!--custom-directive--]>

Template:

It specifies inline HTML template for the directive.

TemplateUrl:

You can also load the template of your directive using a URL of the file containing code for the template. If the inline template is used then this option is not used.

Replace:

It can be set either true or false. Template of the directive gets appended to the parent element in which directive is used. If this option is set false or it can replace the parent element if set to true.

Scope:

It specifies the scope of the directive. It is a very important option that should be set very carefully according to the requirement of the directive you are building. There are three ways in scope of a directive can be set:

  1. Existing scope from directive’s DOM element. If the scope is set to false which is the default value then the scope of the directive will be the same as the scope of the DOM element on which directive is used.
  2. New scope that inherits from the enclosing controller’s scope. If the scope is set to true then a new scope is created for the directive that will inherit all the properties from the parent DOM element scope or we can say it will inherit from the controller’s scope of the DOM enclosing directive.
  3. An isolate scope that will inherit nothing from its parent. This type of scope is generally used in making reusable components.

Controller:

You can specify the controller name or controller function to be used for the directive using this option.

ControllerAs:

The alternate name can be given to the controller using option.

Require:

It specifies the name of the other directive to be used. If your directive depends on upon another directive then you can specify the name of the same using this option.

Link:

It is used to define a function that can be used to programmatically modify template DOM element instances such as adding event listeners, setting up data binding, etc.

Compile:

It can be used to define a function that can be used to programmatically modify the DOM template for features across copies of a directive. It can also return link function to modify the resulting element instances.


Of course, we have control over how our directive will appear in the HTML. Now, let’s see how a typical directive is written in Angular. It is registered in the same way as a controller, but it returns a simple object (directive definition) that has several properties to configure the directive. The following code shows a simple Hello World directive.

var app = angular.module('myApp',[]);
app.directive('customLabel', function () {
 return {
 restrict: 'EA',
 template: '[<div> <[h2>My First Directive</h2]> 
                  <p>This is a simple example.</p>
            </div]>'
 }
});

Like any other Angular app, first I created an angular module then I defined a directive function which simply returns an object with two attributes: ‘restrict‘ and ‘template‘. As you can see, for the restrict property I used EA which means this directive can be used both as an element or an attribute in DOM. Template property contains a string where I have defined the structure of the directive. This directive will render a div containing a heading and a paragraph. The point to be noticed here is the naming convention for the directive, as you can see I have followed the camel-case naming convention because when Angular parse this directive it will split the name with a hyphen. So customLabel will become custom-label and on our HTML template, we will have to use the custom-label name. You may have noticed that all the pre-defined directives of AngularJS come with a ng- prefix. In DOM directive will be used as:

<custom-label></custom-label>

OR

<div custom-label></div>

Example using link and scope Plunker.

Instead of using template we can use templateUrl as a property and give it the name of the file to make our code more readable and easy to be updated.


Understanding scope

In this example, I will try to explain the scope of a directive. All directives have a scope associated with them for accessing methods and data inside the template and link function that I talked about in the last example. Directives don’t create their own scope unless specified explicitly and use their parent’s scope as their own. Like I explained earlier, the values of the scope property decides how the scope will be created and used inside a directive. There are three different values that are set for the scope property of directive. These values can either be true, false or {}.

Scope: false

When the scope is set to false, directive will use its parent scope as its own scope, which means it can access and modify all the data/variables of parent scope. If parent modifies its data in its scope then changes will be reflected in the directive scope. Also, the same will happen if directive will try to modify the data of parent scope since both parent and directive access the same scope both can see changes of each other.

Example from my plunker 

if you change the value of the input box, changes will be reflected on the $scope.name because both of controller and directive accesses the same name from the same controller. So in short, when the scope is set to false, the controller and directive are using the same scope object. Therefore any changes to the controller and directive will be in sync.

Scope: true

When the scope is set to true, a new scope is created and assigned to the directive and scope object is prototypically inherited from its parent’s scope. So, in this case, any change made to this new scope object will not be reflected back to the parent scope object. However, because the new scope is inherited from the parent scope, any change made in the parent scope will be reflected in the directive scope.

Example from my plunker

when any change is made in first text box the directive text boxe will also be updated, but if any change is made in the second text box (which is our directive) then changes will not be reflected in the first text box. First text box access data directly from the controller but the second text box access data in its new scope due to prototypal inheritance.

Scope : { }

When the scope is set to Object literal {}, then a new scope object is created for the directive. But this time, it will not inherit from the parent scope object, it will be completed detached from its parent scope. This scope is also known as Isolated scope. The advantage of creating such type of directive is that they are generic and can be placed anywhere inside the application without polluting the parent scope.

so if we change the previous example’s scope to be an empty object ‘{}’ the directive text box will be empty.

In this case a new scope is created that has no access to its parent scope object and hence data will not be bound.


As I have previously stated, if you set the scope of a directive to Object literal {}, then an isolated scope is created for the directive which means directive has no access to the data/variables or methods of the parent scope object. This could be very useful if you are creating a re-usable component, but in most of the cases we need some kind of communication between directive and parent scope and also want that directive should not pollute the scope of the parent. So isolated scope provides some filters to communicate or exchange data between parent scope object and directive scope object. To pass some data from parent scope to directive scope we need to some properties to the Object literal that we set to scope property. Let’s see the syntax first then I will explain them.

scope: {
    varibaleName1:'@attrName1',
    varibaleName2:'=attrName2',
    varibaleName3:'&attrName3'
}

OR,

scope: {
    attrName1:'@',
    attrName2:'=',
    attrName3:'&'
}

There are three options available to communicate data from parent to the directive in isolated scope.

@: Text binding or one-way binding or read-only access. It is one way binding between directive and parent scope, it expects mapped the attribute to be an expression( {{ }} ) or string. Since it provides one-way binding so changes made in parent scope will be reflected in directive scope but any change made in directive scope will not be reflected back in the parent scope.

=: Model binding or two-way binding. It is two-way binding between parent scope and directive, it expects the attribute value to be a model name. Changes between parent scope and directive scope are synced.

&: Method binding or behavior binding. It is used to bind any methods from parent scope to the directive scope so it gives us the advantage of executing any callbacks in the parent scope.

Example from my plunker 


The link Function and Scope

The template produced by a directive is meaningless unless it’s compiled against the right scope. By default a directive does not get a new child scope. Rather, it gets the parent’s scope. This means that if the directive is present inside a controller it will use that controller’s scope.

To utilize the scope, we can make use of a function called link. This is configured by the link property of the definition object. Let’s change our helloWorld directive so that when the user types a color name into an input field, the background color of Hello World text changes automatically. Also, when a user clicks on the text Hello World, the background color should reset to white. The HTML markup is shown below.

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color" />
  <hello-world/>
</body>

The modified helloWorld directive is shown below:

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    replace: true,
    template: '<p style="background-color:{{color}}">Hello World',
    link: function(scope, elem, attrs) {
      elem.bind('click', function() {
        elem.css('background-color', 'white');
        scope.$apply(function() {
          scope.color = "white";
        });
      });
      elem.bind('mouseover', function() {
        elem.css('cursor', 'pointer');
      });
    }
  };
});

Notice the link function used in the directive. It takes three arguments:

  • scope – The scope passed to the directive. In this case it’s the same as the parent controller scope.
  • elem – The jQLite (a subset of jQuery) wrapped element on which the directive is applied. If you have included jQuery in the HTML before AngularJS is included, this becomes jQuery wrapped instead of jQLite. As the element is already wrapped with jQuery/jQLite, there is no need to again wrap it inside $() for DOM manipulations.
  • attrs – An object representing normalized attributes attached to the element on which the directive is applied. For example, you can attach attributes in HTML like: <hello-world some-attribute></hello-world> and access it in the link function as attrs.someAttribute.

The link function is mainly used for attaching event listeners to DOM elements, watching model properties for changes, and updating the DOM. In the previous directive snippet, we attached two listeners, click and mouseover. The clickhandler resets the background color of the <p>, while the mouseover handler changes the cursor to pointer. The template has an expression {{color}} which changes whenever the model color changes in the parent scope, thereby changing the background color of Hello World. Here is a plunker demonstrating this concept.

The compile Function

The compile function is used to perform any DOM transformation before the link function runs. It accepts the following arguments.

  • tElement – The element on which the directive is applied.
  • attrs – The normalized list of attributes declared on the element.

Just note that the compile function does not have access to the scope, and must return a link function. But, if there is no compile function you can configure the link function as usual. The compile function can be written as:

app.directive('test', function() {
  return {
    compile: function(tElem,attrs) {
      //do optional DOM transformation here
      return function(scope,elem,attrs) {
        //linking function here
      };
    }
  };
});

Most of the time, you will be working with the link function only. This is because most of the directives are concerned with registering event listeners, watchers, updating the DOM, etc., which are done inside the link function. Directives like ng-repeat, which need to clone and repeat the DOM element several times, use the compile function before the linkfunction runs. This leads to a question of why two separate functions are needed. Why can’t we have just one? To answer this we need to understand how directives are compiled by Angular!


How Directives are Compiled

When the application bootstraps, Angular starts parsing the DOM using the $compile service. This service searches for directives in the markup and matches them against registered directives. Once all the directives have been identified, Angular executes their compile functions. As previously mentioned, the compile function returns a link function which is added to the list of link functions to be executed later. This is called the compile phase. If a directive needs to be cloned multiple times (e.g. ng-repeat), we get a performance benefit as the compile function runs once for the cloned template, but the link function runs for each cloned instance. That’s why the compile function does not receive a scope.

After the compile phase is over the linking phase, where the collected link functions are executed one by one, starts. This is where the templates produced by the directives are evaluated against correct scope and are turned into live DOM which react to events!

Advertisements