Directives Explained

The goal of this chapter is to explicitly lay out all of the options and capabilities that directives have to offer when building mature client-side applications.

Directive Definition

The simplest way to think about a directive is that it is simply a function that we run on a particular DOM element. The directive function can provide extra functionality on the element.

For instance, the ng-click directive gives an element the ability to listen for the click event and run an Angular expression when it receives the event. Directives are what makes the Angular framework so powerful, and, as we’ve seen, we can also create them.

A directive is defined using the .directive() method, one of the many methods available on our application’s Angular module.

angular.module('myApp')
.directive('myDirective',
function($timeout, UserDefinedService) {
  // directive definition goes here
})

The directive() method takes two arguments:

name (string)

The name of the directive as a string that we’ll refer to inside of our views.

factory_function (function)

The factory function returns an object that defines how the directive behaves. It is expected to return an object providing options that tell the $compile service how the directive should behave when it is invoked in the DOM.

angular.module('myApp', [])
.directive('myDirective', function() {
  // A directive definition object
  return {
    // directive definition is defined via options
    // which we'll override here
  };
});

We can also return a function instead of an object to handle this directive definition, but it is best practice to return an object as we’ve done above. When a function is returned, it is often referred to as the postLink function, which allows us to define the link function for the directive. Returning a function instead of an object limits us to a narrow ability to customize our directive and, thus, is good for only simple directives.

When Angular bootstraps our app, it will register the returned object by the name provided as a string via the first argument. The Angular compiler parses the DOM of our main HTML document looking for elements, attributes, comments, or class names using that name when looking for these directives. When it finds one that it knows about, it uses the directive definition to place the DOM element on the page.

<div my-directive></div>

To avoid collision with future HTML standards it’s best practice to prefix a custom directive with a custom namespace. Angular itself has chosen the ng- prefix, so use something other than that. In the examples that follow, we’ll use the my- prefix (e.g., my-directive).

The factory function we define for a directive is only invoked once, when the compiler matches the directive the first time. Just like the .controller function, we invoke a directive’s factory function using $injector.invoke.

When Angular encounters the named directive in the DOM, it will invoke the directive definition we’ve registered, using the name to look up the object we’ve registered. At this point, the directive lifecycle begins, starting with the $compile method and ending with the `link method. We’ll dive into the specifics of this process later in this chapter.

Let’s look at all the available options we can provide to a directive definition.

A JavaScript object is made up of keys and values. When the value for a given key is set to a string, boolean, number, array, or object, we call the key a property. When we set the key to a function, we call it a method.

The possible options are shown below. The value of each key provides the signature of either the method or the type we can set the property to:

angular.module('myApp', [])
.directive('myDirective', function() {
  return {
    restrict: String,
    priority: Number,
    terminal: Boolean,
    template: String or Template Function:
      function(tElement, tAttrs) (...},
    templateUrl: String,
    replace: Boolean or String,
    scope: Boolean or Object,
    transclude: Boolean,
    controller: String or
      function(scope, element, attrs, transclude, otherInjectables) { ... },
    controllerAs: String,
    require: String,
    link: function(scope, iElement, iAttrs) { ... },
    compile:  // return an Object OR the link function
              // as in below:
      function(tElement, tAttrs, transclude) {
        return {
          pre: function(scope, iElement, iAttrs, controller) { ... },
          post: function(scope, iElement, iAttrs, controller) { ... }
        }
        // or
        return function postLink(...) { ... }
    }
  };
});

Restrict (string)

restrict is an optional argument. It is responsible for telling Angular in which format our directive will be declared in the DOM. By default, Angular expects that we will declare a custom directive as an attribute, meaning the restrict option is set to A.

The available options are as follows:

E (an element)
<my-directive></my-directive>
A (an attribute, default)
<div my-directive="expression"></div>
C (a class)
<div class="my-directive: expression;"></div>
M (a comment)
<-- directive: my-directive expression -->

These options can be used alone or in combination:

angular.module('myApp', [])
.directive('myDirective', function() {
  return {
    restrict: 'EA' // either an element or an attribute
  };
});

In this case, we can declare the directive as an attribute or an element:

<-- as an attribute -->
<div my-directive></div>
<-- or as an element -->
<my-directive></my-directive>

Attributes are the default and most common form of directive because they will work across all browsers, including older versions of Internet Explorer, without having to register a new tag in the head of the document. See the chapter on Internet Explorer for more information on this topic.

In Angular 1.3 directives now match elements by default as well.

Avoid using comments to declare a directive. This format was originally introduced as a way to create directives that span multiple elements. This approach was especially useful, for example, when using ng-repeat inside a <table> element; however, as of Angular 1.2, ng-repeat provides ng-repeat-start and ng-repeat-end as a better solution to this problem, minimizing the need for the comment form of a directive even more so. If you are curious, however, take a look at the Chrome developer tools elements tab when using ng-repeat to see comments being used under the hood.

Element or Attribute?

Use an element when creating something new on the page that will encapsulate a self-contained piece of functionality. For example, if we’re creating a clock (and couldn’t care less about supporting old versions of Internet Explorer) we’d make a clock directive and declare it in the DOM like so:

<my-clock></my-clock>

Doing so tells users of our directive that we’re specifying a whole piece of our application. Our clock is not decorating or augmenting a pre-existing clock; instead, it’s declaring a whole unit. While we could have used an attribute in this scenario (Angular doesn’t care), we’ve chosen to use an element because it clarifies our intent.

Use an attribute when decorating an existing element with data or behavior. Using our clock example, let’s pretend we’re interested in an analog version of the clock:

<my-clock clock-display="analog"></my-clock>
 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

Ready to master AngularJS?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now