Use Cases of AngularJS Directives

AD, please don't block.

A directive is essentially a function executed when the AngularJS compiler finds its declaration into the DOM. The function can do almost anything, but as common task we can consider defining a behavior or executing a DOM transformation. A directive can be presented as an attribute, an element name, a class name, or a name in a comment. AngularJS comes with a rich built in set of directives, that can be even extended.

In this post, I assume that you have at least a basic knowledge of AngularJS, and AngularJS directives, and I will show you some typical use cases for custom directives; in this way I will take the occasion to try the main properties of the directive definition object.

Basic syntax for custom directives

Before the use cases, the basic syntax to create a custom directive. For all the code samples in this page I started from the angular-seed template. Starting from the angular-seed skeleton, it is quite easy to extract a model to begin to implement custom directives.

<!doctype html>
<html ngApp="myApp">
  <head>
    <title>AngularJS examples</title>
  </head>
  <body>
    <div my-first-directive></div>

    <script src="lib/angular/angular.js"></script>
    <script src="js/app.js"></script>
    <script src="js/directives.js"></script>
  </body>
</html>

After including the directive in the html code, we have to teach to AngularJS what to do when encounters the directive.

The angular-seed template suggests to accomplish this operation in a different javascript file; so in the js/directives.js file, I wrote something like:

angular.module('myApp.directives', []).
  directive('myFirstDirective', function(injectables) {
    // return the directive link function.
    return function(scope, element, attrs) {
      // do stuff here
    }
  });

Now, a final step is required; in the main module, the one from which depends the entire application, we have to specify the dependencies. So in the js/app.js file, I wrote:

// No dependencies... this is not the case!
// angular.module('myApp', []);

// With dependencies:
angular.module('myApp', ['myApp.directives']);

Now I will follow these steps to write a custom directive.

Custom directives use cases

At the moment I see two principal scenarios, in which the use of directives could be very useful.

  • Directives could be used to increment the reusability of code. Directives indeed could be used to split the code of a complex app into more simple parts, which could be reused also in others pages, or even in others projects. A typical example could be an e-commerce web app: in this kind of app, there are some components that can’t miss, like the cart, the item page etc. All these parts could be realized through directives.

  • Directives could be used to save the two way data binding, when AngularJS is used in conjunction with third party modules, eg. a jQuery plugin.

Templating through directives

Let’s start with a simple demo.

See the demo by Bruno Scopelliti (@brunoscopelliti) on CodePen.

The previous example is a super-simplified e-commerce site; it just consists of a search result page, and the shopping cart.

For the sake of simplicity, it is just possible to add an item to the cart, clicking on the special link; and when this is done, the count of the items in the cart will increase.

In this example I defined two custom directives.

In every e-commerce web site there must be a shopping cart; it is usually present in every page of the site, so it’s for sure a good idea to create a reusable template to model our shopping cart. We could do this thanks to AngularJS directives.

I started including in the html page the new custom directive:

<shopping-cart></shopping-cart>

Since I used the directive as a new html element, in the directive definition object I have to specify a value for the restrict property. Others properties allow to specify the template (made with real html elements) with which replace the shopping-cart element.

The second directive I wrote is probably more interesting to watch. I used this directive to model each result in the search result page.

<div class="item" ng-repeat="i in items">
  <div item-card item-title="" item-src="" item-price=""></div>
</div>

In the directive definition object of the item-card directive new properties appear.

The more interesting thing to note is the scope property in the directive definition object. It allows to create a new isolate scope. The isolate scope takes an object hash which defines a set of local scope properties derived from the parent scope.

scope: {
  title: '@itemTitle',
  price: '@itemPrice',
  src: '@itemSrc'
}

This scope override the scope defined in the controller; this means that now is not possible to access the variables and the functions defined in the scope of the main controller. This is actually a problem, cause now

<a ng-click="buyItem(title, price);">Add to cart</a>

won’t works.

To resolve this situation I set the controller property, to specify a new controller specific for the isolate scope created before. This controller could be used in the same way we use the main controller.

controller: function ($scope, $element, $attrs, $location) {
  $scope.addToCart = function (t, p) {
    // get the scope associated with the main controller
    var mainScope = angular.element("#main").scope();
    mainScope.buyItem(t, p);
    return false;
  };
}

And changing the template this way:

<a ng-click="addToCart(title, price);">Add to cart</a>

This example could be used as starting point to show more sophisticated features of AngularJS, and maybe someday I will do this; but meanwhile let’s pass to the second use case.

Saving two way data binding through directives

If the first use case could be seen as an optimization of the code, there is another case in which the use of custom directives is not an option.

When your app grows up in complexity, sooner or later comes the time you would like to include a jQuery plugin, or another external library.

This could break the two way data binding (strong point of AngularJS) established between model and view.

See the demo by Bruno Scopelliti (@brunoscopelliti) on CodePen.

The solution to avoid this unpleasant inconvenience, as shown in the above example, is to wrap the plugin around an AngularJS directive, in conjunction with the using of the following methods:

  • $watch(watchExpression, listener, objectEquality)

    Registers a listener callback to be executed whenever the watchExpression changes (docs).

// when the data changes, drawPlot() is executed
scope.$watch('data', function () {    
  drawPlot();
}, true);
  • $apply(exp)

    Execute an expression in angular from outside of the angular framework. For example from browser DOM events, setTimeout, XHR or third party libraries (docs).

scope.$apply(function () {
  scope.data = [
    // classified
  ];
});