Directives Demystified: 3 Simple Examples

Dave Ceddia bio photo By Dave Ceddia Comment

More than any other part of Angular, directives can be the most bewildering. In the beginning they seem like magic, and writing custom directives looks hard.

In this article, we’re going to look at 3 simple directives. After building them, you’ll be ready to tackle writing your own. Let’s get started.

Example 1: Hello World

First up, the simplest possible example: the venerable Hello World, expressed as a directive.

JavaScript
angular.directive('helloWorld', function() {
  return {
    restrict: 'E',
    template: '<div>Hello world!</div>'
  };
});
HTML
<h1>
  <hello-world></hello-world>
</h1>
Plunker

The angular.directive call creates a directive named helloWorld. Angular will turn the camelCased name into a dash-cased element named hello-world.

The function passed in to angular.directive can be injected with any services you might need. We’re not using any yet.

This function returns a directive definition object which can contain many properties. We’re only using 2:

  • restrict: 'E' means this directive will only work as an Element. Other possible values are ‘A’ for attribute, ‘C’ for class, or ‘M’ for comment. You can combine them too, like restrict: 'AE'.

  • template specifies the HTML that should be inserted into this directive. This HTML is pretty simple, but if it gets more complex, you may want to pull it out into a separate template and refer to it with templateUrl instead.

Example 2: User’s full name

Imagine you’re building an app that has a User model with firstname and lastname. If you want to display their full name somewhere, you could do it this way:

<span>{{ user.firstname }} {{ user.lastname }}</span>

But this isn’t easily reusable, and it could get messy if you need the name in multiple places.

What if your boss comes and says “Hey, can we format the names like ‘Lastname, Firstname’? I think that looks more professional.” Or even, “We’re expanding our feature set by allowing users to put in their middle initial.” You might have a lot of HTML to change.

Wouldn’t it be better if you could write it this way?

<user-fullname user="user"></user-fullname>

Let’s make a directive to do that.

Javascript
angular.directive('userFullname', function() {
  return {
    restrict: 'E',
    template: '<span>{{ user.firstname }} {{ user.lastname }}</span>',
    scope: {
      user: '='
    }
  };
});
Plunker

You’ll recognize restrict and template from the last directive. The only new property is scope.

  • scope: { creates a new isolate scope for this directive. This means:

    a) you can’t access any variables from parent scopes

    b) any variables you put on scope inside this directive will not pollute any parent scopes

    It is, quite literally, isolated.

  • user: '=' binds the attribute user into a variable on your directive’s scope called user. The = means it’s a 2-way binding. There are like 17 different characters you can put there (ok it’s only 4), but we’ll keep it simple and ignore those for now. I find = works fine 90% of the time.

Example 3: User status (with a controller)

Here we’ll create a directive for a user’s account status. This is a little more complicated than their name, because here we’re adding a controller and calling it in the view.

Javascript
angular.directive('userStatus', function() {
  return {
    restrict: 'E',
    template: '<span><i ng-class="us.iconCss()"></i> {{ us.user.status }}</span>',
    scope: {
      user: '='
    },
    controllerAs: "us",
    bindToController: true,
    controller: function() {
      var vm = this;
      vm.iconCss = function() {
        return vm.user.status === "enabled" ? 
          "fa fa-check" : "fa fa-times";
      };
    }
  };
});
HTML
<h1>
  <user-status user="currentUser"></user-status>
</h1>
Plunker

There are a couple new things going on here:

  • controllerAs: "us" – Assigns the controller object to $scope.us. If you’re used to putting variables directly on scope, this will be new to you. I won’t go into it right now, but you can read more about it here.

  • bindToController: true – Binds scope variables to the controller. In combination with controllerAs, this lets us access the user variable as us.user within the view.

  • controller – The controller itself. It’s an inlined function here, for brevity, but in real life you’d pull this out into a real controller, and refer to it by it’s name as a string like controller: "UserStatusCtrl".

Example 4 (BONUS!): Nested directives

One more step to make this set of directives even easier to use: we’ll package them together into a single user-info directive.

Javascript
angular.directive('userInfo', function() {
  return {
    restrict: 'E',
    template: 
      '<span>' +
        '<user-status user="uic.user"></user-status>' +
        '<user-fullname user="uic.user"></user-fullname>' +
      '</span>',
    scope: {
      user: '='
    },
    controllerAs: "uic",
    bindToController: true,
    controller: function() {
      // Empty controller, just for binding
    }
  };
});
HTML
<user-info user="currentUser"></user-info>
Plunker

It looks the same! (as it should). But the HTML contains a little less code. As far as the directive definition object, there’s nothing new here.

Where to go from here?

You’ve got the basics down now. Go off and create a few directives to get it under your fingers. But after that, what’s next?

Components

Here’s some advice: think of your directives as little pluggable components. It doesn’t matter if you’ll only use it once – if it’s a discrete piece of functionality, make it a directive.

Making directives is like extracting methods from your HTML. Where you might extract a chunk of JS into a function to improve readability, you can do the same in HTML by making a directive.

“Thinking in components” will help you transition to Angular 2, both in code and mindset.

Ramp up Complexity As-Needed

The examples here will suffice for most basic components. When you eventually need something more powerful, read about the other features of directives.

Extracting templates is probably the first thing you’ll want to do. I left them inline to keep the examples succinct, but in real usage I always pull them out into an HTML file and refer to them with templateUrl.

The link function is the next thing to explore: it’s the place to set up watches on attributes and do any DOM manipulation before the element renders.

The scope attribute can use bindings other than just =, as I mentioned earlier. Those can be useful in special cases. The Angular doc for $compile is the place to look for that. The Angular docs for ‘directive’ are not as helpful for deciphering the options on the directive definition object.

But mostly, go practice

Go build a bunch of directives. You’ll be good at it in no time!

Choosing a framework is difficult.

Learning React? Sign up and get my React Learning Timeline PDF, and also weekly-ish articles about React, Angular, and JavaScript.

A few emails per month — unsubscribe any time.
comments powered by Disqus