Angular2 - Class - DirectiveMetadata

本来想看的是Directive里的NgControlGroup

由于开头的exportAs 参数直接引导到了DirectiveMetadata

源码里大段的sample和解释,写的很细


首先看下指令的注入

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. —注入普通的service

@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 {@link QueryList}, which updates its contents as children are added,
removed, or moved by a directive that uses a {@link 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 {@link 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 –可能找不到,就给null

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.


开始参数解读

先来总体的构造

  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}
})

  • selector: string; – 就是个css选择器,定位指令的

    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.

  • inputs:string[]
    关于inputs 这里有两个概念
    • directiveProperty specifies the component property where the value is written.
    • bindingProperty specifies the DOM property where the value is read from.

直接举例 inputs: ['bankName', 'id: account-id']
id 就是directiveProperty , account-id 是bindingProperty
就是从host里的 bindingProperty 给 当前class里的 就是directiveProperty
如果像bankName一样没有bindingProperty,那默认bindingProperty和directiveProperty一样

get inputs(): string[] {
return isPresent(this._properties) && this._properties.length > 0 ? this._properties :
this._inputs;
}
get properties(): string[] { return this.inputs; }
private _inputs: string[];
private _properties: string[];

从代码看,inputs无法直接赋值,只能get,也就是只能从构造中赋值

properties 已废弃 被inputs替换


  • outputs: string[]
    和inputs 类似有2个概念,就不举例了

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

    同inputs 只有 get

    get outputs(): string[] {
    return isPresent(this._events) && this._events.length > 0 ? this._events : this._outputs;
    }
    get events(): string[] { return this.outputs; }
    private _outputs: string[];
    private _events: string[];

  • host: {[key: string]: string};

    • host 绑定 events

    直接来例子吧

     @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也就是指令所在的那个元素
    给他的click 绑定了一个方法onClick。 利用$event参数来进一步操作

    • Property Bindings
      来例子

      @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);

    给指令所在的元素的class 中的 valid/invalid 赋值。

    • Attributes 赋值
      这个能力只能针对一些特殊的 Attributes
@Directive({
selector: '[my-button]',
host: {
'role': 'button'
}
})
class MyButton {
}

给role 赋值 button

  • providers: any[]

一组要注入的对象(class)

class Greeter {
greet(name:string) {
return 'Hello ' + name + '!';
}
}

@Directive({
selector: 'greet',
bindings: [
Greeter
]
})
class HelloWorld {
greeter:Greeter;

constructor(greeter:Greeter) {
this.greeter = greeter;
}
}

bindings 废弃,改用providers

   get providers(): any[] {
return isPresent(this._bindings) && this._bindings.length > 0 ? this._bindings :
this._providers;
}
/** @deprecated */
get bindings(): any[] { return this.providers; }
private _providers: any[];
private _bindings: any[];
  • exportAs: string;
    可以在模板中应用指令里的变量

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

    @Directive({
    selector: 'child-dir',
    exportAs: 'child'
    })
    class ChildDir {
    exprotAsVar = "exportAs Text";
    }

    @Component({
    selector: 'main',
    template: `<child-dir #c="child"></child-dir> {{c.exprotAsVar}}`,
    directives: [ChildDir]
    })
    class MainComponent {
    }
  • queries: {[key: string]: any};
    源码中说了两种query

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

    view query的例子

   
@Component({
selector: 'child-component',
template: '<div>{{a}}</div>'
})
class ChildComponent {
a = "hha";
}

@Component({
selector: 'someDir',
queries: {

viewChildren: new ViewChildren(ChildComponent)
},
template: '<child-directive><child-component></child-component></child-directive>',
directives: [ChildComponent]
})
class SomeDir {

viewChildren: QueryList<ChildComponent>;

ngAfterViewInit() {
var b = this.viewChildren;
}
}

viewChildren 是去query 当前组件中的 View 里 是否有对应类型的指令或者子组件
在ngAfterViewInit 中viewChildren 已经得到了
可以操作如下

 this.viewChildren.toArray()[0].a 
>"hha"

再说 Content queries
content 就和本组件的view 和class无关了, 他是去找任何用了此组件tag的内嵌content里去query

举例: SomeDir 中要去query 类型是ChildComponent的组件

当你在某个组件中用了

 <someDir>
<child-component>
</child-component>
</someDir>

这种结构,他就query到了.

  

@Component({
selector: 'child-component',
template: '<div>{{a}}</div>'
})
class ChildComponent {
a = "hha";
}


@Component({
selector: 'someDir',
queries: {
contentChildren: new ContentChildren(ChildComponent)
},
template: '<div>in someDir</div> ',

})
class SomeDir {
contentChildren: QueryList<ChildComponent>;

ngAfterContentInit() {
var a = this.contentChildren;
}


}

ContentChildren 是在ngAfterContentInit 里才能获取到的

如下

this.contentChildren.first
>ChildComponent {a: "hha"}