Angular Directives
Have you ever been jealous of Angular's ability to create new names for HTML tags and attributes? Directives like ng-repeat
and ng-model
make Angular incredibly useful. As it turns out, you can define your own custom directives with angular. There are a couple main reasons why you would want to create a custom directive:
- You want to use the same HTML code in multiple places in your app.
- You want to add special functionality/listeners to DOM elements.
Directives as Reusable Elements
Because a directive is just a special HTML tag name or attribute (or class name), it can be used anywhere that you write HTML. For example, you may want to show your name and contact information on every page of your app. To accomplish this you decide to use a directive:
// app.js
angular.module('myApp', [])
.directive('myInfo', function () {
return {
template: '<span>Warren Buffett -- reach me at warren.buffett@gmail.com</span>'
};
});
// somewhere in your html code
<my-info></my-info>
Angular will insert your template
code into your my-info
tag. You can also use directives as HTML attributes, like this:
<div my-info></div>
It is also possible to use a directive as a class name, but this is generally not good practice:
<div class="my-info"></div>
Note: This can only be done if the restrict
option on the directive is set to 'C'
.
Directive Types
If you want your directive to be used only as an HTML attribute, you can specify that using the restrict
option. You directive declaration would look something like this:
.directive('myInfo', function () {
return {
template: '<span>Warren Buffett -- reach me at warren.buffett@gmail.com</span>',
restrict: 'A'
};
});
With the restrict
option set to 'A'
, the directive would only work if referenced like this:
<div my-info></div>
Here are the different uses for restrict
from the Angular docs:
The restrict option is typically set to:
- 'A' - only matches attribute name
- 'E' - only matches element name
- 'C' - only matches class name
- 'M' - only matches comment with the following format:
<!-- directive: directive-name -->
These restrictions can all be combined as needed:
- 'AEC' - matches either attribute or element or class name
-- Angular Docs
Directive Templates
Directive templates can be in string format, but if they are large they should be included in separate HTML files. This is accomplished with the templateUrl
option:
// app.js
angular.module('myApp', [])
.directive('myInfo', function () {
return {
restrict: 'E',
templateUrl: 'info.html'
};
});
// info.html
<span>Warren Buffett -- reach me at warren.buffett@gmail.com</span>
Directives with Controllers
The great thing about directives is that they can be placed anywhere in HTML, including within the scope of a controller:
// app.js
angular.module('myApp', [])
.controller('myController', function ($scope) {
$scope.name = 'Warren Buffett';
$scope.email = 'warren.buffett@gmail.com';
})
.directive('myInfo', function () {
return {
template: '<span>{{name}} -- reach me at {{email}}</span>',
restrict: 'E'
};
});
// html file
<div ng-controller="myController">
<my-info></my-info>
</div>
If the $scope
changes, the directive will change as well.
Directive Scope
Directives tied to a controller $scope
might be useful with certain controllers, but it would be better if the data passed to a directive wasn't tied in a strict way to the $scope
. For example, what if you want to display the information of multiple users at the same time? You could make two different controllers that are side-by-side and have different data. But that would be messy. Instead, you could pass different data to two different instances of the same directive. To do this, you must modify the scope of the directive. Each directive has its own individual scope. (This scope will be the same as its containing controller if not specified otherwise.) Directive scope is assigned using the scope
option:
// app.js
angular.module('myApp', [])
.controller('myController', function ($scope) {
$scope.warren = {
name: 'Warren Buffett',
email: 'warren.buffett@gmail.com'
};
$scope.dieter = {
name: 'Dieter Rams',
email: 'dieter.rams@vitsoe.com'
};
})
.directive('myInfo', function () {
return {
restrict: 'A',
scope: {
'contactInfo': '=myInfo'
},
template: '<span>{{contactInfo.name}} -- reach me at {{contactInfo.email}}</span>'
};
});
// html file
<body ng-controller="myController">
<div my-info="warren"></div>
<div my-info="dieter"></div>
</body>
Giving each directive its own scope allows you to use directives in ng-repeat
!
Functional Directives
The second reason to create a directive is to manipulate the DOM in a way that is not available out-of-the-box. Manual DOM manipulation should always be done through directives, not controllers or anything else. The directive API offers a function called link
to aid in manipulating the DOM.
Here is the definition of link
from the docs:
Directives that want to modify the DOM typically use the
link
option.link
takes a function with the following signature,function link(scope, element, attrs) { ... }
where:
scope
is an Angular scope object.element
is the jqLite-wrapped element that this directive matches.attrs
is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values.
In other words, scope
is the same scope object that you're already used to. element
is essentially a jQuery object relating to the directive element in the DOM, so you can call functions like .on('click', ...)
on it. attrs
is just an object which stores the values of HTML attributes on the directive element.
Angular does not come with an ng-enter
directive for input fields, so the only way to know when the enter key has been pressed is to use ng-submit
on a form. That is not always a good solution, so I'm going to implement a myEnter
directive:
// app.js
angular.module('myApp', [])
.controller('myController', function ($scope) {
$scope.greet = function () {
$scope.greeting = 'Hello, ' + $scope.name;
};
})
.directive('myEnter', function () {
var link = function (scope, element, attrs) {
// bind is a jQuery function
element.on('keydown', function (event) {
// check which key was pressed (the enter key has a code of 13)
if (event.which === 13) {
scope.$apply(function () {
// run the code passed to my-enter in the html
scope.$eval(attrs.myEnter);
});
event.preventDefault();
}
});
};
return {
restrict: 'A',
link: link
};
});
// html
<input type="text" my-enter="greet()" ng-model="name"> {{ greeting }}
Parts of this code might seem confusing on first examination. To learn more about scope.$apply
visit this article:
http://www.sitepoint.com/understanding-angulars-apply-digest/