DirectiveMetadata

export class DirectiveMetadata

exported from angular2/core defined in angular2/src/core/metadata/directives.ts (line 4)

Directives allow you to attach behavior to elements in the DOM.

DirectiveMetadatas with an embedded view are called ComponentMetadatas.

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:

  1. For each directive, the ElementInjector attempts to resolve the directive's constructor arguments.
  2. 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.

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 ViewMetadata:

  1. Dependencies on the current element
  2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
  3. Dependencies on component injectors and their parents until it encounters the root component
  4. 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:

To inject element-specific special objects, declare the constructor parameter as:

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 { id:string; }

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 { constructor() { } }

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 { constructor(dependency: Dependency) { expect(dependency.id).toEqual(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 { constructor(@Host() dependency: Dependency) { expect(dependency.id).toEqual(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 { constructor(@Query(Dependency) dependencies:QueryList<Dependency>) { } }

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 { constructor(@Query(Dependency, {descendants: true}) dependencies:QueryList<Dependency>) { } }

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 { constructor(@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{ text:string; overlay:Overlay; // NOT YET IMPLEMENTED overlayManager:OverlayManager; // NOT YET IMPLEMENTED constructor(overlayManager:OverlayManager) { this.overlay = overlay; } onMouseEnter() { // exact signature to be determined this.overlay = this.overlayManager.open(text, ...); } onMouseLeave() { this.overlay.close(); this.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 ViewMetadata, 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'] }) export class Unless { viewContainer: ViewContainerRef; templateRef: TemplateRef; prevCondition: boolean; constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef) { this.viewContainer = viewContainer; this.templateRef = templateRef; this.prevCondition = null; } set unless(newCondition) { if (newCondition && (isBlank(this.prevCondition) || !this.prevCondition)) { this.prevCondition = true; this.viewContainer.clear(); } else if (!newCondition && (isBlank(this.prevCondition) || this.prevCondition)) { this.prevCondition = false; this.viewContainer.create(this.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.

Constructor

constructor({selector, inputs, outputs, properties, events, host, bindings, providers, exportAs,
             queries}?: {
  selector?: string,
  inputs?: string[],
  outputs?: string[],
  properties?: string[],
  events?: string[],
  host?: {[key: string]: string},
  providers?: any[],
  /** @deprecated */ bindings?: any[],
  exportAs?: string,
  queries?: {[key: string]: any}
})

Not Yet Documented

Members

selector : string

The CSS selector that triggers the instantiation of a directive.

Angular only allows directives to trigger on CSS selectors that do not cross element boundaries.

selector may be declared as one of the following:

  • element-name: select by element name.
  • .class: select by class name.
  • [attribute]: select by attribute name.
  • [attribute=value]: select by attribute name and value.
  • :not(sub_selector): select only if the element does not match the sub_selector.
  • selector1, selector2: select if either selector1 or selector2 matches.

#

Suppose we have a directive with an input[type=text] selector.

And the following HTML:

<form> <input type="text"> <input type="radio"> <form>

The directive would only be instantiated on the <input type="text"> element.

inputs : string[]

Enumerates the set of data-bound input properties for a directive

Angular automatically updates input properties during change detection.

The inputs property defines a set of directiveProperty to bindingProperty configuration:

  • directiveProperty specifies the component property where the value is written.
  • bindingProperty specifies the DOM property where the value is read from.

When bindingProperty is not provided, it is assumed to be equal to directiveProperty.

(live demo)

The following example creates a component with two data-bound properties.

@Component({ selector: 'bank-account', inputs: ['bankName', 'id: account-id'], template: ` Bank Name: {{bankName}} Account Id: {{id}} ` }) class BankAccount { bankName: string; id: string; // this property is not bound, and won't be automatically updated by Angular normalizedBankName: string; } @Component({ selector: 'app', template: ` <bank-account bank-name="RBC" account-id="4747"></bank-account> `, directives: [BankAccount] }) class App {} bootstrap(App);

properties : string[]

Not Yet Documented

outputs : string[]

Enumerates the set of event-bound output properties.

When an output property emits an event, an event handler attached to that event the template is invoked.

The outputs property defines a set of directiveProperty to bindingProperty configuration:

  • directiveProperty specifies the component property that emits events.
  • bindingProperty specifies the DOM property the event handler is attached to.

(live demo)

@Directive({ selector: 'interval-dir', outputs: ['everySecond', 'five5Secs: everyFiveSeconds'] }) class IntervalDir { everySecond = new EventEmitter(); five5Secs = new EventEmitter(); constructor() { setInterval(() => this.everySecond.emit("event"), 1000); setInterval(() => this.five5Secs.emit("event"), 5000); } } @Component({ selector: 'app', template: ` <interval-dir (every-second)="everySecond()" (every-five-seconds)="everyFiveSeconds()"> </interval-dir> `, directives: [IntervalDir] }) class App { everySecond() { console.log('second'); } everyFiveSeconds() { console.log('five seconds'); } } bootstrap(App);

events : string[]

Not Yet Documented

host : {[key: string]: string}

Specify the events, actions, properties and attributes related to the host element.

Host Listeners

Specifies which DOM events a directive listens to via a set of (event) to method key-value pairs:

  • event: the DOM event that the directive listens to.
  • statement: the statement to execute when the event occurs. If the evaluation of the statement returns false, then preventDefaultis applied on the DOM event.

To listen to global events, a target must be added to the event name. The target can be window, document or body.

When writing a directive event binding, you can also refer to the $event local variable.

(live demo)

The following example declares a directive that attaches a click listener to the button and counts clicks.

@Directive({ selector: 'button[counting]', host: { '(click)': 'onClick($event.target)' } }) class CountClicks { numberOfClicks = 0; onClick(btn) { console.log("button", btn, "number of clicks:", this.numberOfClicks++); } } @Component({ selector: 'app', template: `<button counting>Increment</button>`, directives: [CountClicks] }) class App {} bootstrap(App);

Host Property Bindings

Specifies which DOM properties a directive updates.

Angular automatically checks host property bindings during change detection. If a binding changes, it will update the host element of the directive.

(live demo)

The following example creates a directive that sets the valid and invalid classes on the DOM element that has ngModel directive on it.

@Directive({ selector: '[ngModel]', host: { '[class.valid]': 'valid', '[class.invalid]': 'invalid' } }) class NgModelStatus { constructor(public control:NgModel) {} get valid { return this.control.valid; } get invalid { return this.control.invalid; } } @Component({ selector: 'app', template: `<input [(ngModel)]="prop">`, directives: [FORM_DIRECTIVES, NgModelStatus] }) class App { prop; } bootstrap(App);

Attributes

Specifies static attributes that should be propagated to a host element.

#

In this example using my-button directive (ex.: <div my-button></div>) on a host element (here: <div> ) will ensure that this element will get the "button" role.

@Directive({ selector: '[my-button]', host: { 'role': 'button' } }) class MyButton { }

providers : any[]

Defines the set of injectable objects that are visible to a Directive and its light DOM children.

Simple Example

Here is an example of a class that can be injected:

class Greeter { greet(name:string) { return 'Hello ' + name + '!'; } } @Directive({ selector: 'greet', bindings: [ Greeter ] }) class HelloWorld { greeter:Greeter; constructor(greeter:Greeter) { this.greeter = greeter; } }

bindings : any[]

Not Yet Documented

exportAs : string

Defines the name that can be used in the template to assign this directive to a variable.

Simple Example

@Directive({ selector: 'child-dir', exportAs: 'child' }) class ChildDir { } @Component({ selector: 'main', template: `<child-dir #c="child"></child-dir>`, directives: [ChildDir] }) class MainComponent { }

queries : {[key: string]: any}

Configures the queries that will be injected into the directive.

Content queries are set before the ngAfterContentInit callback is called. View queries are set before the ngAfterViewInit callback is called.

#

@Component({
  selector: 'someDir',
  queries: {
    contentChildren: new ContentChildren(ChildDirective),
    viewChildren: new ViewChildren(ChildDirective)
  },
  template: '<child-directive></child-directive>',
  directives: [ChildDirective]
})
class SomeDir {
  contentChildren: QueryList<ChildDirective>,
  viewChildren: QueryList<ChildDirective>

  ngAfterContentInit() {
    // contentChildren is set
  }

  ngAfterViewInit() {
    // viewChildren is set
  }
}