AngularJS, code.explode, Coding

Creating a Simple AngularJS Directive

One of the core features of AngularJS is the ability to extend the HTML Syntax with directives. Directives allow you to componentize segments of HTML into a single tag that can be re-used throughout your AngularJS app. So instead of repeating the following HTML all over your code:

    <div class="row-fluid" ng-repeat="adjunct in adjuncts | filter:search | orderBy:'name'">
        <div class="span1">{{adjunct.Name}}</div>
        <div class="span1">{{adjunct.Type}}</div>
        <div class="span1">{{adjunct.Use}}</div>
        <div class="span1">{{adjunct.Time}}</div>
        <div class="span1">{{adjunct.Amount}}</div>
        <div class="span1">{{adjunct.UseFor}}</div>
        <div class="span1">{{adjunct.Notes}}</div>
        <div><a href="#/editadjunct/{{adjunct._id.$oid}}"><i class="icon-pencil"></i></a></div>
    </div>

With a directive you can use the following HTML:

    <div data-display-adjunct class="row-fluid" ng-repeat="adjunct in adjuncts | filter:search | orderBy:'name'">

Now anywhere where you would use the above HTML, you can use the directive instead and be consistent across you entire site. This also saves you time when changes are required. If the amount of data that needs to be displayed changes you only need to update the directive and your done. You don’t have to hunt around your HTML files looking for every occurrence to change.

Directives allow you to not only specify what data from your app to bind to, but they can also handle the work of transforming the data into different formats, removing that responsibility from your controller, helping you to keep to the single responsibility principle.

If you apply this simple principle on bigger scales, directives can be used to build rich components that bind to your app’s data and reduce the amount of HTML you need to create across your entire site.

In this article, I’m going to show you how to write a simple directive that generates an image tag with a user’s avatar from the Gravatar site, http://en.gravatar.com/

The Gravatar Image Directive Overview

Gravatar allows you to upload an image and link it to your email address, so it can be used by the various social networking and forum sites on the web.

If you head over to the Gravatar developer documentation on how to request images, http://en.gravatar.com/site/implement/images/. You’ll see that you need to create a md5 hash of the user’s email and then append that to a Url along with parameters for size, rating, and default image.

So the Url for my email address jlavin@jimlavin.net gets converted into the following image tag:

    <img src="http://www.gravatar.com/avatar/80b03752791145a3fdd027b154d7b42b?s=40&r=pg&d=404">

So we are going to write a directive that will take an email address and inject the resulting image tag into the page. When we are complete the directive will look something like this:

    <gravatar-image data-email="currentBrewer.Email" data-size="40" ></gravatar-image>

Defining the Directive

To start out we’ll need to create a new JavaScript file and call it gravatardirective.js and we’ll start with the basic skeleton for a directive.

1: 'use strict';
2:
3: /*
4: * An Simple AngularJS Gravatar Directive
5: *
6: * Written by Jim Lavin
7: * http://codingsmackdown.tv
8: *
9: */
10:
11: angular.module('ui-gravatar', []).
12:     directive('gravatarImage', function () {
13:         return {
14:             restrict:"EAC",
15:             link:function (scope, elm, attrs) {
16:         }};
17:     });

So to define a directive we need to create an Angular module, provide a namespace, ‘ui-gravatar’, and include any dependencies the directive might require. In our case there are no dependencies so we can send in an empty array as seen on line 11.

Next we are going to use chaining to declare our directive by appending .directive after the module declaration.

In Angular, you can call your directive in several different ways. You can call it by using an element, an attribute, a class or as a comment. The following are all valid ways to call a directive:

    <gravatar-image data-email="email></gravatar-image>
    <div gravatar-image="email"></div>
    <div class="gravatar-image: email"></div>

Angular will then replace the dash and convert the name into Camel case format, so we need to ensure we name our directive using Camel case, gravatarImage. so while Angular is parsing the DOM it can find our directive. You can see this on line 12 of the code above.

Next we provide a function that will return a Directive Definition Object that tells Angular about our directive. You can see the declaration starting on line 12 continuing through to line 17.

By default Angular will restrict calling directives to attributes only. To change this you can declare a restrict property and return the ways you want the directive to be called. I do this on line 14 by returning “EAC”, which means you should be able to call directive using an element, attribute or class.

Next, we are going to declare a link function that will be used to update the DOM as the data we are binding to changes. You can see the declaration on line 15.

The link function can take up to 4 parameters; the scope to be used by the directive, the instance element the directive will be modifying, the element attributes and an optional controller instance if a directive on the element has defined one.

Simple Data Binding

So we are going to need an email address in order to formulate the Url to retrieve an avatar image. To do this we can use simple data binding and send the data value or object we want to bind to as part of the directive’s declaration as shown below:

    <div gravatar-image="email" ></div>

To access the data, we’ll need to use the attributes instance to setup a watch on the data and then as we are notified of changes, we can update the DOM accordingly. The reason we have to setup a watch is because initially the data will come through as undefined as Angular compiles the DOM, only when it runs the linker to bind the data to the DOM will a value be present in our scope.

To do this we will need to modify our directive to add a watch function which will be called each time the value is updated.

1: 'use strict';
2:
3: /*
4: * An Simple AngularJS Gravatar Directive
5: *
6: * Written by Jim Lavin
7: * http://codingsmackdown.tv
8: *
9: */
10:
11: angular.module('ui-gravatar', []).
12:     directive('gravatarImage', function () {
13:         return {
14:             restrict:"EAC",
15:             link:function (scope, elm, attrs) {
16:                 // by default the values will come in as undefined so we need to setup a
17:                 // watch to notify us when the value changes
18:                 scope.$watch(attrs.gravatarImage, function (value) {
19:                     elm.text(value);
20:             });
21:         }};
22:     });

Starting on line 18 we declare the watch function and each time the data updates, we set the element’s text to the updated value.

One thing to notice is we are supplying the value supplied in the attributes instance to the watch, not the scope value, since the scope value is undefined when the link function is called and the watch will never be triggered.

To finish our first version of the directive let’s create the Url and append an image tag to the element so it will retrieve the Gravatar image.

1: 'use strict';
2:
3: /*
4: * An Simple AngularJS Gravatar Directive
5: *
6: * Written by Jim Lavin
7: * http://codingsmackdown.tv
8: *
9: */
10:
11: angular.module('ui-gravatar', []).
12:     directive('gravatarImage', function () {
13:         return {
14:             restrict:"EAC",
15:             link:function (scope, elm, attrs) {
16:                 // by default the values will come in as undefined so we need to setup a
17:                 // watch to notify us when the value changes
18:                 scope.$watch(attrs.gravatarImage, function (value) {
19:                     // let's do nothing if the value comes in empty, null or undefined
20:                     if ((value !== null) && (value !== undefined) && (value !== '')) {
21:                         // convert the value to lower case and then to a md5 hash
22:                         var hash = md5(value.toLowerCase());
23:                         // construct the tag to insert into the element
24:                         var tag = '<img alt="" src="http://www.gravatar.com/avatar/' + hash + '?s=40&r=pg&d=404" />'
25:                         // insert the tag into the element
26:                         elm.append(tag);
27:                     }
28:             });
29:         }};
30:     });

Now, when the watch function is called, I evaluate the value and if it is not empty, null or undefined, I force it to lower case, create a md5 hash of the email, format the image tag and append it to the element. Which gives us the following result when we use it an app.

SampleWebPage_1

Adding Configuration Attributes

Now, our directive works great but what if you want to use different sizes of the avatar at different places in your app or maybe you want to only allow PG rated avatars since you have people of all ages using your app and you don’t want to offend anyone.

We could create various version of the directive and name them gravatar-image-small or gravatar-image-pg, but that won’t do. We need to provide those values when we call the directive.

To do this we’ll need to send additional configuration elements to the directive so we can dynamically tell Gravatar what we need.

So, let’s change our directive to look for specific attributes on the element so we can specify the size, rating and default avatar.

We are going to look for three attributes in our directive; size, rating and default. Now we can do this many ways but the simplest is to use data-xxx attributes in the element and then we can read the values using attrs.xxx. This is because Angular will strip off “data-“ from any attributes as is creates the attributes instance, which make it easy for us.

so now when we call our directive we can use the following HTML:

    <div gravatar-image data-email="email" data-size="120" data-rating="pg" data-default="404" ></div>

The code for directive now includes the logic to parse the attributes use their values or supply defaults if they are missing. I’ve also moved the data we are binding to it’s own attribute data-email, which will make things easier if we decide to use the directive as an element.

Below is the new directive, coded to retrieve the attributes and format the Url accordingly:

1: 'use strict';
2:
3: /*
4: * An Simple AngularJS Gravatar Directive
5: *
6: * Written by Jim Lavin
7: * http://codingsmackdown.tv
8: *
9: */
10:
11: angular.module('ui-gravatar', []).
12:     directive('gravatarImage', function () {
13:         return {
14:             restrict:"EAC",
15:             link:function (scope, elm, attrs) {
16:                 // by default the values will come in as undefined so we need to setup a
17:                 // watch to notify us when the value changes
18:                 scope.$watch(attrs.email, function (value) {
19:                     // let's do nothing if the value comes in empty, null or undefined
20:                     if ((value !== null) && (value !== undefined) && (value !== '')) {
21:                         // convert the value to lower case and then to a md5 hash
22:                         var hash = md5(value.toLowerCase());
23:                         // parse the size attribute
24:                         var size = attrs.size;
25:                         // default to 40 pixels if not set
26:                         if((size=== null) || (size == undefined) || (size == '')){
27:                             size = 40;
28:                         }
29:                         // parse the ratings attribute
30:                         var rating = attrs.rating;
31:                         // default to pg if not set
32:                         if((rating === null) || (rating === undefined)|| (rating === '')){
33:                             rating = 'pg';
34:                         }
35:                         // parse the default image url
36:                         var defaultUrl = attrs.default;
37:                         if((defaultUrl === null) || (defaultUrl === undefined)|| (defaultUrl === '')) {
38:                             defaultUrl = '404';
39:                         }
40:                         // construct the tag to insert into the element
41:                         var tag = '<img alt="" src="http://www.gravatar.com/avatar/' + hash + '?s=' + size + '&r=' + rating + '&d=' + defaultUrl + '" />'
42:                         // insert the tag into the element
43:                         elm.append(tag);
44:                     }
45:                 });
46:             }};
47:         });

Now, starting at line 24 we parse the size attribute and if it has a value we use it, otherwise we use a default value.

We do the same thing for the rating and default attributes starting on lines 30 and 36.

Line 41 now reflects the tag with all of the various configurations supplied. You might also notice in our watch statement that we changed the watch value from attrs.gravatarImage to attrs.email since we are now passing the data binding value in using the data-email attribute.

Wrap Up

Writing directives in AngularJS can be hard, but by starting out simple, you will learn how things work in AngularJS and as you experiment more you will be able to build very complex components that can extend HTML, which will save you time and decrease the amount of HTML your team needs to write.

The source code for this article, along with a sample app can be found on Github at https://github.com/lavinjj/angularjs-gravatardirective

I hope this tutorial helps you get started writing AngularJS Directives. Drop me a comment on other AngularJS topics you’d like to see more tutorials on.

Standard

7 thoughts on “Creating a Simple AngularJS Directive

  1. Not sure if you were aware, but where you are showing the code for

    var tag = ”

    The html markup is being parsed by the users browser so that it shows up with a broken image between the quotes and we can’t read what you meant to show.

  2. Pingback: angularjs 学习大礼包大赠送【权威英文版】! _ Angular _ 小G的大Q

  3. Pingback: AngularJS-Learning | Nisar Khan

  4. Pingback: AngularJS – load images using REST API calls

  5. Pingback: AngularJS - load images using REST API calls - Tech Forum Network

  6. Pingback: How could you become zero to hero in AngularJS? | Milap Bhojak

Got a comment? I'd love to hear it!