As we all know, the DDO (Directive Definition Object) returned by the Angular 1.x directive callback
function has a require
property, that sort of establishes dependency of one
directive on others. This is a convenient way of building components that
contain multiple directives where one directive may depend on the functionality
of others. In practice, the require
property simply tells Angular compiler
where a particular directive controller should be search for.
Recently, reviewing AngularJS 1.3 source code, I’ve realized that there were some
additional options for the require
property besides what I already knew about.
Let’s take this trivial example of defining a UI “Tabs” component in Angular via two directives:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
angular.module('components.tabs', [])
.directive('tabset', function() {
return {
restrict: 'EA',
transclude: true,
scope: {},
controller: function($scope) {
// ...
}
}
}).directive('tab', function() {
return {
restrict: 'EA',
require: '^tabset'
link: function (scope, elem, attrs, ctrl) {
var tabsetController = ctrl;
// ...
}
}
});
What require: '^tabset'
line does here is tells Angular compiler that our tab
directive “depends” on the tabset
directive via controller defined in the tabset
directive. When require
property is present, Angular attempts to locate this
controller object in the search path and then inject it into the tab
directive’s
link
function as the fourth argument (please note, when using require
property,
both directives share the same instance of the controller). Where exactly Angular searches for
this controller object depends on the prefix of the directive name (e.g. ^
in this case)
given in the require
property value of DDO.
Here is a list of all prefixes supported by the require:
property and their
corresponding effect on the search path:
Prefix | Search path | Error raised if controller not found? | Example | Link function receives as 4th argument |
---|---|---|---|---|
(no prefix) | Current DOM element that directive is applied to | Yes | tabset | Controller object, if found. |
? | Current DOM element that directive is applied to | No | ?tabset | Controller object, if found, `null` otherwise |
^ | Current DOM element that directive is applied to and its parents | Yes | ^tabset | Controller object, if found. |
^^ | Parent elements of the DOM element that the directive is applied to | Yes | ^^tabset | Controller object, if found. |
?^ | Current DOM element that directive is applied to and its parents | No |
?^tabset ^?tabset |
Controller object, if found, `null` otherwise |
?^^ | Parent elements of the DOM element that the directive is applied to | No |
?^^tabset ^^?tabset |
Controller object, if found, `null` otherwise |
As you’ve probably already noticed, the ?
in the prefix simply makes the search optional, so
the compiler doesn’t throw any errors and the link
function simply receives
null
as the fourth argument, if such controller is not found.
Also, worth noting that if multiple directives are required, the require
property of the directive can take an array argument:
1
2
3
4
5
6
7
8
9
10
11
12
// ...
.directive('tabs', function() {
return {
restrict: "EA",
require: ['^tabset', 'ngModel'] // <- this directive depends on tabset and ngModel directives
link: function (scope, elem, attrs, ctrls) {
var tabsetController = ctrls[0]
var ngModelController = ctrls[1];
// ...
}
};
});
In this case, the injected 4th argument in the link
function will be an array of
controller objects in corresponding order.
Follow @demisx1 for more tips and best practices.
Today my environment was:
- Angular 1.3.2
- Mac OS X 10.10.1