• US Inquiries / 1 877 517 6540
  • Canadian Inquiries / 1 866 206 4644
submit to reddit

March 18, 2016 / KB-021

Custom Directives in AngularJS

Check out these related webinars...

Unit Testing AngularJS Code
Get Ready for Angular 2.0
Introduction to AngularUI and UI-Bootstrap
Building AngularJS Apps with Grunt.js
Hybrid Mobile Development with AngularJS and Ionic
JavaScript for Web 2.0 Development
AngularJS

Objectives

Key objectives of this chapter

  • What are directives?

  • Defining custom directives

  • Using custom directives

What are Directives?

  • An AngularJS directive is a JavaScript object that encapsulates reusable view layer logic.

  • They generate HTML markup, manipulate DOM elements and apply styles.

  • Avoid writing any complicated business logic in directives. Do so in services.

  • Directives, like services are singletons. Only one instance of a directive is created per page.

  • Be careful about storing any state in directives.

  • AngularJS ships with a number of directives like ng-app and ng-repeat. You can define your own.

Directive Usage Types

  • Depending on how a directive is defined, you can use it in a page in these ways:

  • Element directive. Such as <my-directive></my-directive>.

  • Attribute directive. Such as <div my-directive>.

  • CSS directive. Such as <div class="my-directive">

  • Comment directive. Such as <!-- directive: my-directive -->

  • By default, a directive can be used as an element or attribute. This is also the best practice.

  • You can restrict how a directive can be used as a part of its definition.

  • When used as an element, you must provide the closing tag. Otherwise, some browsers may ignore it.

  • Good: <my-directive></my-directive>

  • Bad: <my-directive/>

Directive Naming Convention

  • A directive is registered using its normalized name. Such as "myDirective".

  • In the HTML markup, it must be referred to using one of these names:

  • "my-directive". As in <div my-directive> or <my-directive>. This is the common approach.

  • "data-my-directive". As in <div data-my-directive>. This is the HTML5 way to add custom attributes.

  • "my:directive".

  • "my_directive".

  • "x-my-directive".

  • It's a good idea to prefix custom directives so that the names don't collide with other toolkits. For example, "pcsGallery" instead of just "Gallery".

Defining a Custom Directive

  • Use the directive() method of a module to register a custom directive.

  • The directive() method takes as input:

  • The normalized name of the directive.

  • A factory function that returns an instance of the directive.

angular.module("SimpleApp", \[\])
.directive("myDirective", function(\$http) {
  return {
    template: "<h1>Hello World</h1>"
  };
});
  • You can inject services into the factory function.

Using the Directive

<div ng-app="SimpleApp">
  <my-directive></my-directive>
  <my-directive>Body ignored</my-directive>
</div>

Will output this DOM tree:

<div>
  <my-directive><h1>Hello
  World</h1></my-directive>
  <my-directive><h1>Hello
  World</h1></my-directive>
</div>
  • The directive creation function is called only once and only one instance of the directive is created.

  • By default, the body of a custom directive is discarded. The template is the sole provider of content. Advanced components can enable the use of the body and children elements.

  • The DOM is different when If you use a directive as an attribute:

Creates:

Hello World

Scope of a Directive

  • By default, a directive does not create its own scope. It simply works in the context of whatever scope it was used for in the page.

  • In this example, the directive uses the scope of the TestCtrl controller.

angular.module("SimpleApp", \[\])
.directive("myDirective", function() {
  return {
    template: "<h1>Hello {{**planet**}}</h1>"
    };
  })
  .controller("TestCtrl", function(\$scope) {
    $scope.**planet** = "Mars";
});
<div ng-controller="TestCtrl">
<my-directive></my-directive>
<input type="text" ng-model="planet"/>
</div>

Scope of a Directive

In the example above, the controller adds a planet property to the scope. This property is visible to the template of the directive, since the directive simply uses the scope of the controller. When the page is first rendered, we see "Hello Mars".

The input text box lets us change the value of the planet property. If we type in Pluto, the directive will show "Hello Pluto".

Isolating Scope

  • By default, a directive works with the scope of the parent controller. This has two major problems:

  • The directive has to know the names of the properties in the parent's scope (planet in our example). This tightly couples the directive with the controller making it less reusable.

  • The directive can not be used more than once in any meaningful way. For example, if the controller has different planets in scope, how can we use the directive for different planets?

  • The solution is for the directive to have its own scope and provide a way to copy certain properties from the parent scope to the scope of the directive. This is called scope isolation.

Creating a Scope for the Directive

  • To create a new scope for a directive add a new property called scope to the directive instance.

  • Example:

.directive("myDirective", function() {
  return {
    template: "<h1>Hello {{planet}}</h1>",
    scope: {}
  };
})

Copying Data to a Directive's Scope

.directive("myDirective", function() {
  return {
    template: "<h1>Hello {{planet}}</h1>",
    scope: {
    planet: "="
    }
    };
})
.controller("TestCtrl", function(\$scope) {
  \$scope.planet1 = "Mars";
  \$scope.planet2 = "Earth";
  });
<div ng-controller="TestCtrl">
  <my-directive planet="planet1"> </my-directive>
  <my-directive planet="planet2"> </my-directive>
</div>

Copying Data to a Directive's Scope

The scope property of a directive object sets up the private scope of the directive. Here, we have added one property to the scope called planet with the value "=". The prefix "=" tells AngularJS that planet is an attribute of the directive. The value of the attribute will be an expression that will be evaluated against the outer scope (that of the controller). The evaluated value of the expression will be stored in the planet property of the inner scope.

We use the directive first with the planet attribute set to "planet1". System takes the value of planet1 property from the outer scope – "Mars" – and stores it as the planet property of the directive's scope.

The second time we use the directive, the planet attribute is set to "planet2". System copies "Earth" to the planet property of the inner scope.

Using External Template File

  • If a directive needs to generate complex HTML markup, consider creating a separate HTML template file.
.directive("myDirective", function() {
  return {
    templateUrl: "my-directive.html",
    scope: {
    planet: "="
    }
  };
})
<!-- my-directive.html -->

<h1>Hello {{planet}}</h1>

Manipulating a DOM Element

  • For a directive to manipulate an existing DOM element, use the link function. The following directive sets the border style:
.directive("myBordered", function() {
return {
  link: function(scope, element, attrs, controller) {
    element.attr("style", "border: thin solid black");
    }
  };
});
<div my-bordered>
<h2>Hello World</h2>
<p>Now is the winter of our discontent.</p>
</div>

The Link Function

  • The link function gets called after the directive instance is associated with a DOM element.

  • The function receives these arguments:

  • The scope of the directive.

  • A jQuery like object representing the DOM element where the directive is applied. It provides a subset function of jQuery such as attr() and on(). This is known as the jqLite API. It is basically an array of DOM elements.

  • A map containing all the attributes of the element.

  • The controller of the directive if any. This is discussed in another chapter.

Event Handling from a Link Function

  • A directive can handle DOM events by attaching handlers from the link function. The following will show a border when the mouse enters an element.

  • To attach a handler, you can use the on() method made available by jqLite, or, you can use the addEventListener() standard DOM method.

.directive("liveBorder", function() {
  return {
    link: function(scope, element, attrs) {
      element.on("mouseenter", function(event) {
      element.attr("style", "border: thin solid black");
      });
      element.on("mouseleave", function(event) {
      element.attr("style", "");
      });
    }
  };
});
<div live-border>Hello</div>

Wrapping Other Elements

  • By default, if a directive provides its own template, then the body of the element is ignored. To include the body of the directive in the HTML markup output by its template, you will need to use the transclusion feature.
.directive("myQuote", function() {
  return {
    transclude: true,
    template: "<p>This was said:</p>" +
    "<div ng-transclude style='margin-left: 50px; padding-left: 10px;
    border-left: thick solid gray'></div>",
    };
});

<my-quote>
<h2>Hello World</h2>
<p>Now is the winter of our discontent.</p>
</my-quote>

Wrapping Other Elements

When you enable transclusion, AngularJS will insert the body of the custom directive as a child of the element in the template with the ng-transclude attribute.

Accepting a Callback Function

  • A directive can generate its own events by calling callback functions.

  • A callback function is provided by the parent controller. The "&" scope isolate type calls the callback in the context of the parent scope.

.directive("myDirective", function() {
  return {
    scope: {
      myCallback: "&" //Note the scope isolate type
    },
    link: function(scope, element, attrs) {
      element.on("click", function(event) {
        scope.myCallback(); //Call the callback
        });
      }
    };
})

Supplying Callback Function

  • Add the callback function in the controller's scope and register it with the directive in the HTML.

  • You can supply any scope property as a parameter to the callback.

.controller("testCtrl", function($scope) {
  $scope.message = "Hi there!";
  $scope.clickHandler = function(str) {
  console.log("I was clicked: " + str);
  }
});
<div my-directive my-callback="clickHandler(message)">
Click me.
</div>
  • As shown above, you can supply any controller scope variable as argument to the function.

Supplying Argument to Callback

  • For the directive to supply arguments to a callback you will need to obtain the actual callback function by unwrapping it.
.directive("myDirective", function() {
return {
  scope: {myCallback: "&"},
  link: function(scope, element, attrs) {
    element.on("click", function(event) {
      scope.myCallback()("Hello", "World");
      });
    }
  };
})
  • When registering the callback, leave out the parenthesis.
<div my-directive my-callback="clickHandler">
Click me.
</div>

Summary

  • Reusable view layer logic is captured in custom directives.

  • By default, a custom directive uses the same scope as the parent which reduces its reusability. Most directives will need to setup their own scope using scope isolation.

  • The link function of a directive is called right after the singleton instance of a directive is associated with a DOM element. You can manipulate the element or attach event handlers from the link function.

  • To include the body of the directive in the HTML markup output by its template, you will need to use the transclusion feature.

Related Webinars

Related Training

WA2425
AngularJS Training
AngularJS Programming
Toronto
Vancouver
Instructor Led Virtual
Calgary
Ottawa