Directives allow you to attach behavior to elements in the DOM.
Directives with an embedded view are called Components.
A directive consists of a single directive annotation and a controller class. When the directive's selector matches elements in the DOM, the following steps occur:
-
For each directive, the
ElementInjector
attempts to resolve the directive's constructor arguments. -
Angular instantiates directives for each matched element using
ElementInjector
in a depth-first order, as declared in the HTML.
Understanding How Injection Works
There are three stages of injection resolution.
-
Pre-existing Injectors:
-
The terminal
Injector
cannot resolve dependencies. It either throws an error or, if the dependency was specified asOptional
, returns null. -
The platform injector resolves browser singleton resources, such as: cookies, title, location, and others.
-
-
Component Injectors: Each component instance has its own
Injector
, and they follow the same parent-child hierarchy as the component instances in the DOM. -
Element Injectors*: Each component instance has a Shadow DOM. Within the Shadow DOM each element has an
ElementInjector
which follow the same parent-child hierarchy as the DOM elements themselves.
When a template is instantiated, it also must instantiate the corresponding
directives in a depth-first order. The current ElementInjector
resolves
the constructor dependencies for each directive.
Angular then resolves dependencies as follows, according to the order in
which they appear in the View
:
- Dependencies on the current element
-
Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
-
Dependencies on component injectors and their parents until it encounters the root component
- Dependencies on pre-existing injectors
The ElementInjector
can inject other directives, element-specific special
objects, or it can delegate to the parent injector.
To inject other directives, declare the constructor parameter as:
DirectiveType directive
: a directive on the current element only-
@Host() DirectiveType directive
: any directive that matches the type between the current element and the Shadow DOM root. -
@Query(DirectiveType) QueryList<DirectiveType> query
: A live collection of direct child directives. -
@QueryDescendants(DirectiveType) QueryList<DirectiveType> query
: A live collection of any child directives.
To inject element-specific special objects, declare the constructor parameter as:
-
ElementRef element
, to obtain a reference to logical element in the view. -
ViewContainerRef viewContainer
, to control child template instantiation, for Directive directives only -
BindingPropagation bindingPropagation
, to control change detection in a more granular way.
Example
The following example demonstrates how dependency injection resolves constructor arguments in practice.
Assume this HTML template:
<div dependency="1">
<div dependency="2">
<div dependency="3" my-directive>
<div dependency="4">
<div dependency="5"></div>
</div>
<div dependency="6"></div>
</div>
</div>
</div>
With the following dependency decorator and SomeService
injectable class.
@Injectable()
class SomeService { }
@Directive(
selector: 'dependency',
inputs: [
'id: dependency'
]
)
class Dependency {
String id;
}
Let's step through the different ways in which MyDirective
could be
declared...
No injection
Here the constructor is declared with no arguments, therefore nothing is
injected into MyDirective
.
@Directive(selector: 'my-directive')
class MyDirective {
MyDirective();
}
This directive would be instantiated with no dependencies.
Component-level injection
Directives can inject any injectable instance from the closest component injector or any of its parents.
Here, the constructor declares a parameter, someService
, and injects the
SomeService
type from the parent component's injector.
@Directive({ selector: '[my-directive]' })
class MyDirective {
constructor(someService: SomeService) {
}
}
This directive would be instantiated with a dependency on SomeService
.
Injecting a directive from the current element
Directives can inject other directives declared on the current element.
@Directive(selector: 'my-directive')
class MyDirective {
MyDirective(Dependency dependency) {
expect(dependency.id, 3);
}
}
This directive would be instantiated with Dependency
declared at the same
element, in this case [dependency]="3"
.
Injecting a directive from any ancestor elements
Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the current element, the parent element, or its parents.
@Directive(selector: 'my-directive')
class MyDirective {
MyDirective(@Host() Dependency dependency) {
expect(dependency.id, 2);
}
}
@Host
checks the current element, the parent, as well as its parents
recursively. If dependency="2"
didn't exist on the direct parent, this
injection would have returned dependency="1"
.
Injecting a live collection of direct child directives
A directive can also query for other child directives. Since parent
directives are instantiated before child directives, a directive can't
simply inject the list of child directives. Instead, the directive injects
a QueryList
, which updates its contents as children are added,
removed, or moved by a directive that uses a ViewContainerRef
such as a
ngFor
, an ngIf
, or an ngSwitch
.
@Directive(selector: 'my-directive')
class MyDirective {
MyDirective(@Query(Dependency) QueryList<Dependency> dependencies);
}
This directive would be instantiated with a QueryList
which contains
Dependency
4 and Dependency
6. Here, Dependency
5 would not be
included, because it is not a direct child.
Injecting a live collection of descendant directives
By passing the descendant flag to @Query
above, we can include the
children of the child elements.
@Directive({ selector: '[my-directive]' })
class MyDirective {
MyDirective(@Query(Dependency, {descendants: true})
QueryList<Dependency> dependencies);
}
This directive would be instantiated with a Query which would contain
Dependency
4, 5 and 6.
Optional injection
The normal behavior of directives is to return an error when a specified
dependency cannot be resolved. If you would like to inject null on
unresolved dependency instead, you can annotate that dependency with
@Optional()
. This explicitly permits the author of a template to treat
some of the surrounding directives as optional.
@Directive(selector: 'my-directive')
class MyDirective {
MyDirective(@Optional() Dependency dependency);
}
This directive would be instantiated with a Dependency
directive found on
the current element. If none can be/ found, the injector supplies null
instead of throwing an error.
Example
Here we use a decorator directive to simply define basic tool-tip behavior.
@Directive(
selector: 'tooltip',
inputs: [
'text: tooltip'
],
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()'
}
)
class Tooltip{
String text;
Overlay overlay;
OverlayManager overlayManager;
Tooltip(this.overlayManager);
onMouseEnter() {
overlay = overlayManager.open(text, ...);
}
onMouseLeave() {
overlay.close();
overlay = null;
}
}
In our HTML template, we can then add this behavior to a <div> or any other
element with the tooltip
selector, like so:
<div tooltip="some text here"></div>
Directives can also control the instantiation, destruction, and positioning of inline template elements:
A directive uses a ViewContainerRef
to instantiate, insert, move, and
destroy views at runtime.
The ViewContainerRef
is created as a result of <template>
element,
and represents a location in the current view where these actions are
performed.
Views are always created as children of the current ViewMeta
, and as
siblings of the <template>
element. Thus a directive in a child view
cannot inject the directive that created it.
Since directives that create views via ViewContainers are common in Angular,
and using the full <template>
element syntax is wordy, Angular
also supports a shorthand notation: <li *foo="bar">
and
<li template="foo: bar">
are equivalent.
Thus,
<ul>
<li *foo="bar" title="text"></li>
</ul>
Expands in use to:
<ul>
<template [foo]="bar">
<li title="text"></li>
</template>
</ul>
Notice that although the shorthand places *foo="bar"
within the <li>
element, the binding for the directive controller is correctly instantiated
on the <template>
element rather than the <li>
element.
Lifecycle hooks
When the directive class implements some lifecycle-hooks the callbacks are called by the change detection at defined points in time during the life of the directive.
Example
Let's suppose we want to implement the unless
behavior, to conditionally
include a template.
Here is a simple directive that triggers on an unless
selector:
@Directive(
selector: 'unless',
inputs: ['unless']
)
class Unless {
ViewContainerRef viewContainer;
TemplateRef templateRef;
bool prevCondition;
Unless(this.viewContainer, this.templateRef);
set unless(newCondition) {
if (newCondition && (prevCondition == null || !prevCondition)) {
prevCondition = true;
viewContainer.clear();
} else if (!newCondition && (prevCondition == null ||
prevCondition)) {
prevCondition = false;
viewContainer.create(templateRef);
}
}
}
We can then use this unless
selector in a template:
<ul>
<li///unless="expr"></li>
</ul>
Once the directive instantiates the child view, the shorthand notation for the template expands and the result is:
<ul>
<template [unless]="exp">
<li></li>
</template>
<li></li>
</ul>
Note also that although the <li></li>
template still exists inside the
<template></template>
, the instantiated view occurs on the second
<li></li>
which is a sibling to the <template>
element.
- Inheritance
- Object
- Injectable
- Directive
- Implemented by
Constructors
- Directive({String selector, List<String> inputs, List<String> outputs, Map<String, String> host, List providers, String exportAs, Map<String, dynamic> queries })
-
const
Properties
- exportAs → String
-
Defines the name that can be used in the template to assign this directive to a variable.
read-only - hashCode → int
-
Get a hash code for this object.
read-only, inherited - host → Map<String, String>
-
Specify the events, actions, properties and attributes related to the host element.
read-only - inputs → List<String>
-
Enumerates the set of data-bound input properties for a directive
read-only - outputs → List<String>
-
Enumerates the set of event-bound output properties.
read-only - providers → List
-
Defines the set of injectable objects that are visible to a Directive and its light DOM children.
read-only - queries → Map<String, dynamic>
-
Configures the queries that will be injected into the directive.
read-only - runtimeType → Type
-
A representation of the runtime type of the object.
read-only, inherited - selector → String
-
The CSS selector that triggers the instantiation of a directive.
read-only
Operators
-
operator ==(
other) → bool -
The equality operator.
inherited
Methods
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a non-existent method or property is accessed.
inherited -
toString(
) → String -
Returns a string representation of this object.
inherited