Directives (Parts 4 and 5 of the AngularJS - from beginner to expert in 7 steps series)

This post contains parts 4 (directives) and 5 (expressions) of AngularJS - from beginner to expert in 7 steps.

We started our first several entries demonstrating the core components of the application and how to set up an AngularJS app. In this section, we’ll clear up some terminology and dive into a lot of core out-of-the-box functionality that we get with AngularJS.

Throughout this tutorial series, we are building an NPR audio player that will show us the current stories on the show Morning Edition and play them in our browser. To see the fully finished demo, head over here.

4. Directives

So far in this series, we’ve mentioned the phrase directives a few times without diving into what they actually are. A directive is a fancy name for a function that’s attached to a DOM element. Directives have the ability to execute methods, define behavior, attach controllers and $scope objects, manipulate the DOM, and more.

When the browser starts up and starts to parse the HTML (as it does normally), any directives are passed over like any other attribute.

When the AngularJS app is bootstrapped and the Angular compiler starts walking through the DOM tree (starting from the DOM element that declares ng-app, as we mentioned in part 1), it will parse the HTML looking for these directive functions.

When it finds a DOM element with one or more, it will collect, sort, and run the directive functions in priority order.

The priority is determined by the individual directives. You can find more advanced information in our directives article.

Directives handle all of the heavy lifting that AngularJS apps use to be dynamic and responsive. We’ve seen a few examples of directives previously.

ng-model

1
2
<input ng-model="name" name="Name" placeholder="Enter your name"/> <h4>Your name: {{ name }}</h4>

Try it

Your name: {{ name }}

The ng-model directive (which we’ve used in previous sections) is set to bind the value of the input DOM element to the $scope model in the controller. To do that, it sets up a $watch (similar to the JavaScript event listener) on the value.

$watch functions run (if necessary) in the AngularJS event loop (the $digest loop), and the Angular updates the DOM accordingly. Stay tuned for our advanced post on the $digest loop!

In Angular, we use directives to bind behavior to the DOM. How we use our directives is the key to a responsive, dynamic Angular application. There are a lot of default directives that come with AngularJS. Let’s look at a few and how to use them.

Common, built-in directives

{{ expression }}

The double-curly expression directive registers a listener for the expression inside the {{ expression }} using the $watch() function. This function enables Angular to update the view.

What is an expression?

5. Expressions

In order to understand how directives work, we must also understand expressions, so we’re taking a little detour into Part 5 of this series: Expressions. We’ve seen expressions in previous examples: {{ person.name }} and {{ clock }}, for instance.

{{ 8 + 1 }}
{{ person }}
{{ 10 * 3.3 | currency }}

The last example (10 * 3.3 | currency) uses a filter. We’ll talk about filters in depth later in this series.

Expressions are kinda like the result of an eval(javascript) (roughly). They are processed by Angular and, therefore, have these important, distinct properties:

Try it

Try typing “person”, “clock”, or other math expressions like 2 + 4. You can even manipulate the scope: For instance, try things like person.name = "Ari"; person.age = 28; person or clock

Expressions all operate on the containing scope within which they are called. This fact enables you to call variables bound to the containing scope inside of an expression, which, in turn, enables you to loop over variables (we’ll see this with ng-repeat), call a function, or use variables for math expressions from the scope.

Now, back to our directives list…

ng-init

The ng-init directive is a function that runs at bootstrap time (before runtime). It allows us to set default variables prior to running any other functions during runtime:

1
<b ng-init='name = "Ari Lerner"'>Hello, {{ name }}</b>

See it

Hello, {{ name }}

ng-click

The ng-click directive registers a listener with the DOM element. When the DOM listener fires (when a button or link is clicked), Angular executes the expression and updates the view as normal:

1
2
<button ng-click="counter = counter + 1">Add one</button> Current counter: {{ counter }}

See it

Current counter: {{ counter }}

We can also use ng-click to call a function that we’ve made on a controller. For instance:

1
<button ng-click="sayHello()">Say hello</button>

The controller function:

1
2
3
4
5
app.controller('MyController', function($scope) { $scope.sayHello = function() { alert("hello!"); } });

Try it

ng-show / ng-hide

The ng-show and ng-hide directives show or hide a portion of the DOM depending on whether the expression is truthy.

We won’t go into them here, but “truthy” and “falsey” are JavaScript terms with which you’ll want to be familiar.

1
2
3
4
5
6
7
<button ng-init="shouldShow = true" ng-click="shouldShow = !shouldShow">Flip the shouldShow variable</button> <div ng-show="shouldShow"> <h3>Showing {{ shouldShow }}</h3> </div> <div ng-hide="shouldShow"> <h3>Hiding {{ shouldShow }}</h3> </div>

Try it

Showing {{ shouldShow }}

Hiding {{ shouldShow }}

ng-repeat

The ng-repeat directive loads a template for each item in a collection. The template it clones is the element upon which we call ng-repeat. Each copy of the template gets its own scope.

Before we dive into explanation, let’s look at an example:

Let’s say we have a list of the following items in our controller:

1
2
3
4
5
6
$scope.roommates = [ { name: 'Ari'}, { name: 'Q'}, { name: 'Sean'}, { name: 'Anand'} ];

We can use ng-repeat to loop through them in our view:

1
2
3
<ul> <li ng-repeat="person in roommates">{{ person.name }}</li> </ul>

See it

  • {{ item.name }}

We can also loop through key-value lists in the ng-repeat directive to slightly modify the expression. For instance, if we have a list of people and their favorite colors:

1
2
3
4
5
$scope.people = { 'Ari': 'orange', 'Q': 'purple', 'Sean': 'green' }

We can use the repeat expression: (key, value) in object, like so:

1
2
3
4
<ul> <li ng-repeat="(name, color) in people">{{ name }}'s favorite color is {{ color }} </li> </ul>

See it

  • {{ name }}’s favorite color is {{ color }}

These are only a few of the built-in directives that AngularJS provides out of the box. AngularJS makes it very easy to create your own directives, as well. Check out our guide on creating your own custom directives here.

Directives in our app

We left our audio app last time by just fetching the list of latest audio programs that the NPR API sends us back:

1
$scope.programs = data.list.story;

Now that we know how to iterate over a list of programs, we can simply take our programs and iterate over them to produce a listing using ng-repeat, as we did above:

1
2
3
4
5
<ul id="programs_list" class=""> <li ng-repeat="program in programs"> <span class="large-12">{{ program.title.$text }}</span> </li> </ul>

The NPR API gives us back a listing with the title + $text. This syntax is specific to the NPR API, not to AngularJS itself.

Now we have a listing with the programs listed and their titles, but no way to click and play our titles. Using ng-click, we can add a link to the element:

1
2
3
4
5
<ul id="programs_list" class=""> <li ng-repeat="program in programs" ng-click="play(program)"> <span class="large-12">{{ program.title.$text }}</span> </li> </ul>

This step will bind the action play to clicking the list DOM element. Now, once we create the play action in our PlayerController, we’ll have a fully functioning audio app:

1
2
3
4
5
6
7
8
9
// format.mp4.$text is the route to the mp4 file from the NPR api $scope.play = function(program) { if ($scope.playing) $scope.audio.pause(); var url = program.audio[0].format.mp4.$text; audio.src = url; audio.play(); // Store the state of the player as playing $scope.playing = true; }

Although this app is fully functional, it is not very pretty, and the code itself will become bloated and difficult to manage, especially as we start to add new functionality. We can create our own directives to help reduce this complexity.

To learn more about creating custom directives, check out our deep directives post here.

To create a custom directive, we’ll use the directive function on the app object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.directive('nprLink', function() { return { restrict: 'EA', require: ['^ngModel'], replace: true, scope: { ngModel: '=', play: '&' }, templateUrl: '/views/nprListItem.html', link: function(scope, ele, attr) { scope.duration = scope.ngModel.audio[0].duration.$text; } } });

We won’t cover what all of these options mean, as we’ve covered it in an in-depth post, but they allow us to reference this directive in our HTML and replace that DOM element with the contents of our template (in /views/nprListItem).

Now, in our template we can create the isolated view that our list item will refer to while keeping our main HTML clean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="nprLink row" ng-click="play(ngModel)"> <span class="name large-8 columns"> <button class="large-2 small-2 playButton columns"><div class="triangle"></div></button> <div class="large-10 small-10 columns"> <div class="row"> <span class="large-12">{{ ngModel.title.$text }}</span> </div> <div class="row"> <div class="small-1 columns"></div> <div class="small-2 columns push-8"><a href="{{ ngModel.link[0].$text }}">Link</a></div> </div> </div> </span> </div>

Notice that we refer to the program as ngModel instead of program in the template, due to how we’ve set up our directive.

Now, instead of ‘cluttering’ our main HTML with this specific HTML, we can simply replace it with:

1
2
3
4
5
<ul id="programs_list" class=""> <li ng-repeat="program in programs"> <span npr-link play='play(program)' ng-model="program"></span> </li> </ul>

In the next section, we’ll discuss how services work and how to communicate across the controllers in our app.

The official repository for the beginner series is available as a git repo here: https://github.com/auser/ng-newsletter-beginner-series.

To get this repo locally, ensure that you have git installed, clone the repo, and check out part5 branch. We are using XHR to fetch templates, so you’ll need to run a local server. We’ve included a server script in part5:

1
2
3
git clone https://github.com/auser/ng-newsletter-beginner-series.git git checkout part5 ./bin/server.sh
Get the weekly email all focused on AngularJS. Sign up below to receive the weekly email and exclusive content.
We will never send you spam and it's a cinch to unsubscribe.

Download a free sample of the ng-book: The Complete Book on AngularJS

ng-book: The Complete Book on AngularJS is the canonical AngularJS book available today.

It's free, so just enter your email address and the PDF will be sent directly to your inbox. Mailchimp can take up to an hour to deliver the free sample chapter, but if you don't receive it within the hour, send us an email and we'll manually send them to you!

We'll send you updates about the book, when it updates and other free content.

We will never send you spam and it's a cinch to unsubscribe.

Comments

comments powered by Disqus