Directives in AngularJS


So I thought that I'd make a detailed post about directives in AngularJS. If you've just started out with AngularJS, chances are you are curious to learn about this powerful feature. Directives are great if you want to create your own custom HTML elements. In fact, directives give us a glimpse of the future. In HTML 5, we got new elements and the goal is to increase this in the future so you won't need to use the class attribute so often. Learning to use directives may seem a bit complex for the AngularJS beginner, but trust me, it's really easy once you get the hang of it. Using directives isn't really mandatory when you work with AngularJS, unfortunately this leads to developers not using this feature. I hope to change that with my post, and make you use it if you don't already.

A directive can be written in four different ways; as an element, attribute, comment or class. The best practice is to write it as an element or attribute. Here are the four ways a directive can be written in HTML:

<my-directive></my-directive>
<div my-directive></div>
<!-- directive: my-directive -->
<div class="my-directive"></div>

The directive is loaded into Angular at the start up of a web application. Usually in a bootstrap.js where you load your other Angular components, such as controllers and factories. The directive is loaded like such (line 2):

var myApp = angular.module('myApp', []);
myApp.directive('myDirective', myDirective);
function myDirective() {
    return ""
};

Note that in the HTML we called the directive my-directive, this is transformed to camel case and therefore we load the directive as myDirective in Angular. As you can see, this directive doesn’t do much. It simply returns an empty string. A directive function has up to eleven different attributes that can be used. It has priority, template, templateUrl, replace, transclude, restrict, scope, controller, require, link and compile. By no means will you be using all of those attributes at once, usually you would use three to four attributes depending on your web application. I’m going to give a short description of each attribute. </p>

Priority defines the order of how the directives get compiled, this is the case when you have created multiple directives on a single DOM element. Directives with a greater priority number get compiled first.

Template is used to add your HTML template code that you want to be shown, and templateUrl is a URL to your HTML template.

Replace is used to either replace the directive element with your HTML template (true) or to replace the contents of it (false). This is by default set to false.

Transclusion is a sibling of the isolate scope (which I will explain soon), which means that it's bound to the parent scope. This makes the content of your directive have its own private state. transclude can be set to true (transclude the content) or to 'element' (transclude the whole directive).

Restrict is used to define the type of your directive. It can be set to E (element), A (attribute), M (comment) or C (class). The default value is A.

By using scope, you can define an isolated scope for your directive content. If scope is not used, the directive will use the parent scope by default. Scope can be set to true (new scope created for the directive), false (use parent scope), pass in scope for two-way binding, pass in type and other custom attributes.

Controller is used if you want your directive to have its own controller. You simply set it with the name of the controller that you've loaded in Angular.

Use require if you want to have a dependency on other directives, in that case inject the dependency directive's controller as the fourth argument in the link function (which we will get to shortly). require is set to the name of the dependency directive's controller, if there are several dependency directives, then an array is used containing the controller names.

Link is used to define directive logic, it is responsible for registering DOM listeners and updating the DOM. Link takes in several arguments, and it is here where you define your directive logic. However, this attribute is unnecessary if you are using the controller attribute and got the directive logic inside a controller.

Compile is used to transform the template DOM. ngRepeat is an example of a template DOM that needs to be transformed into HTML. Since most directives do not transform DOM templates, this attribute is not often used. Compile takes in several arguments.

For more information on each of the directive attributes, I suggest checking the AngularJS documentation.

Time for examples of usage, let’s assume we have written the following directive in the HTML:

<my-directive></my-directive>

Now, in the JavaScript this directive is implemented like this (line 3):

var myApp = angular.module('myApp', []);
myApp.directive('myDirective', myDirective);
function myDirective() {
    return {
    restrict: 'EA',
    template: '<div>Hello World!</div>'
    }

I set restrict to 'EA' on purpose, to show you that you can make the directive implementation work on both an element and an attribute if you wish. This is a simple directive that returns a template div with the text “Hello World!”. Now, we can extract this template to a file:

function myDirective() {
    return {
    restrict: 'EA',
    templateUrl: 'helloWorld.html'
    }
};

And the content of helloWorld.html:

<div>Hello World!</div>

Let’s put our directive inside a controller:

<div ng-controller="myController">
<my-directive></my-directive>
</div>

The controller is simple, it looks like this:

myApp.controller(myController, myController);
function myController($scope) {
$scope.message = "Hello World!";
};

Now, we change the directive:

function myDirective() {
    return {
    restrict: 'EA',
    template: ''
    }
};

Can you guess what happens? The directive uses the parent controller and scope, and returns the message defined in the controller - “Hello World!”. Now let’s create a controller specifically for our directive, and call it myDirectiveController:

myApp.controller('myDirectiveController', myDirectiveController);
function myDirectiveController($scope) {
    $scope.directiveMessage = "Hello World!";
};

And change the directive to use this controller and an isolated scope:

function myDirective() {
    return {
    restrict: 'EA',
    scope: true,
    controller: 'myDirectiveController',
    template: ''
    }
};

Again, the directive will return the message “Hello World!”, this time from its own controller. If you want to pass parameters from the parent controller, that’s possible. Let’s assume that the parent controller myController has a messages collection, that we loop through in the HTML:

<div ng-controller="myController"> 
<div ng-repeat="message in messages">
<my-directive message="message"></my-directive>
</div>
</div>

We loop through the collection and pass each message to the directive. The directive then looks like this:

function myDirective() {
    return {
    restrict: 'EA',
    scope: {
    message: '='
    },
    controller: 'myDirectiveController',
    template: ''
    }
};

The message is passed to the directive controller, by setting the message attribute in the scope to ‘=’. This can be done differently, you can type message="myMessage" in the div (instead of message="message"), in that case you need to add message: '=myMessage' in the scope. Using ‘=?’ means that a parameter is optional. Another thing you can do is send in type. You use the type attribute in the HTML, then add type: '@' in the scope.

Another thing you can do of course, is to use the link attribute to define your logic there. Let’s do that:

function myDirective() {
    return {
    restrict: 'EA',
    link: function(scope, element, attributes) {
        element.addClass('alert');
    }
};

Link takes in current scope as argument, the directive element itself and custom attributes that you can pass in. Link uses jQuery Lite, a lighter version of jQuery. You can do jQuery operations inside of this function, in this case we are adding a css class alert to the directive element. You can do a lot of other different things with directives, for more I suggest checking out the AngularJS documentation.

I've explained most of what you need to know about directives. When you write directives, I recommend that you create controllers specifically for the directives. Put all logic in the controllers, and do not use the link function. Avoid jQuery operations and stick to pure Angular. That way you keep your code clean and nicely isolated. I hope that this gives you a good introduction to directives, and helps you get started using them. As you can see, it's not that hard, is it? :)