Software Engineer, Brett Billington, talks about integral aspects of an AngularJS application, as they introduce new functionality into HTML.

Using AngularJS Directives for Custom Elements

Brett Billington -  16 Dec, 2014

Directives are one of the most integral aspects of an AngularJS application, as they introduce new functionality into HTML. For instance, creating a new attribute for special validation behaviour or even a whole new element for displaying a freely-editable list of email addresses that can be placed anywhere.

Using the email address list as an example, take a basic way of displaying a list of addresses for the user to enter their data - several inputs placed one after the other to allow users to enter up to 10 emails maximum. This isn't ideal - it is poorly structured and has lots of duplicate HTML. It also has some inherent problems - what if the user needs more than ten email addresses?

Example:

<div class="form-group">
<label>Email addresses</label>
<input type="email" data-ng-model="emailAddresses[0]" />
</div>
<div class="form-group">
<div class="col-sm-2"></div>
<input type="email" data-ng-model="emailAddresses[1]" />
</div>
<div class="form-group">
<div class="col-sm-2"></div>
<input type="email" data-ng-model="emailAddresses[2]" />
</div>
<div class="form-group">
<div class="col-sm-2"></div>
<input type="email" data-ng-model="emailAddresses[3]" />
</div>
<etc...>

This needs improvement - and this is exactly the kind of situation where directives shine!

With a directive:

<div class="form-group">
<label>Email addresses</label>
<email-address-list data-ng-model="emailAddresses"></email-address-list>
</div>

We're looking at the <email-address-list> element here - one simple element dropped anywhere can reproduce the first example with great improvements - instead of displaying ten inputs one after the other, only one is displayed by default and the user can add/remove extra lines as desired.

A look at its appearance:

Let's start with the basics, assuming we already have a module defined.

.directive('emailAddressList', function () {
return {
templateUrl: 'emailaddresslist.html',
restrict: 'E',
require: 'ngModel',
scope: {},
link: function (scope, element, attributes, ngModelCtrl) {
}
};
});
  • The directive uses 'emailaddresslist.html' for its template.
  • The directive is restricted to being used as an element, hence 'E'.
  • It requires ngModel to be passed in via the element. The directive places data into this model, but it must pre-exist.
  • The scope is isolated, as we want to be able to use this email address list in many places.

Now we're giving the controller some behaviour.

controller: function ($scope) {
$scope.add = function () {
$scope.emailAddresses.push(undefined);
};
$scope.remove = function (index) {
$scope.emailAddresses.splice(index, 1);
};
}
  • The 'add' function adds a new empty item into the email addresses list.
  • The 'remove' function removes an item from the email addresses list according to its index in the array.

These functions control the add/remove buttons on the email address list.

Finally, the link behaviour.

link: function (scope, element, attributes, ngModelCtrl) {
scope.$watch('emailAddresses', function () {
ngModelCtrl.$setViewValue(scope.emailAddresses);
}, true);

ngModelCtrl.$parsers.push(function (viewValue) {
return _.compact(viewValue);
});

ngModelCtrl.$formatters.push(function (modelValue) {
return _.compact(modelValue);
});
}
  • $watch observes the email address list for any changes, and if they happen, the viewed value is set to these changes. The 'true' parameter passed in is a bit special - when an array is watched, typically only the amount of items inside of the array are observed. This 'true' tells $watch to keep an eye on the contents of each item, too.
  • Before $watch changes a value in the view, $parsers is executed. Here, it runs _compact upon the value passed in. _compact is part of Lo-Dash, a utility library. This is a convenience function which removes any falsey values from an array - we don't want empty email addresses in our list! They may be shown to the user, but internally, we don't keep track of them.
  • $formatters is the opposite of $parsers - it is executed when the model value changes, rather than the view. This is called when the model is first set upon navigating to this page, meaning if any empty values are passed in, they're instantly removed.

The controller and link work together to ensure that only valid data is kept in the model. Empty lines are ignored as if they weren't present.

For this example, only the first and third lines will be stored in the model - the second line is ignored as an empty address.

The resulting element is more robust than the original non-directive example, allowing the user to pick out exactly how many email addresses they need - they're not restricted to any specific number! As the code is self-contained, it's also simple to expand upon without having to worry about updating every place where it's used manually.

The template HTML has been excluded for the sake of brevity, so for a look at this directive in action, please see this Plunker example - it also includes an extra requirement that the user must enter at least one email address in the list.

Subscribe to this blog

Use of this website and/or subscribing to this blog constitutes acceptance of the travel.cloud Privacy Policy.

Comments