在 Angular 2 Decorators - part 1 文章中,咱們介紹了 TypeScript 中的四種裝飾器。本文的主要目的是介紹 Angular 2 中常見的內置裝飾器。Angular 2 內置裝飾器分類:git
類裝飾器github
@Component、@NgModule、@Pipe、@Injectable數組
屬性裝飾器angular2
@Input、@Output、@ContentChild、@ContentChildren、@ViewChild、@ViewChildrenapp
方法裝飾器框架
@HostListenerionic
參數裝飾器ide
@Inject、@Optional、@Self、@SkipSelf、@Host函數
import { NgModule, Component } from '@angular/core'; @Component({ selector: 'example-component', template: '<div>Woo a component!</div>' }) export class ExampleComponent { constructor() { console.log('Hey I am a component!'); } }
import { Component, Input } from '@angular/core'; @Component({ selector: 'example-component', template: '<div>Woo a component!</div>' }) export class ExampleComponent { @Input() exampleProperty: string; }
import { Component, HostListener } from '@angular/core'; @Component({ selector: 'example-component', template: '<div>Woo a component!</div>' }) export class ExampleComponent { @HostListener('click', ['$event']) onHostClick(event: Event) { // clicked, `event` available } }
import { Component, Inject } from '@angular/core'; import { MyService } from './my-service'; @Component({ selector: 'example-component', template: '<div>Woo a component!</div>' }) export class ExampleComponent { constructor(@Inject(MyService) myService) { // 與myService: MyService等價 console.log(myService); } }
下面咱們就着重分析一下最經常使用的類裝飾器 @Component ,其它的裝飾器讀者有興趣的話,能夠參考 Component 的分析思路自行分析。動畫
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>`, }) export class AppComponent { name = 'Angular'; }
首先從最簡單的例子入手,咱們都知道採用 TypeScript 開發,爲了保證兼容性最終都會轉換成標準的 ES 5代碼。上面的例子轉成以下的代碼:
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { ... }; define(["require", "exports", "@angular/core"], function (require, exports, core_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var AppComponent = (function () { function AppComponent() { this.name = 'Angular'; } return AppComponent; }()); AppComponent = __decorate([ core_1.Component({ // (1) selector: 'my-app', template: "<h1>Hello {{name}}</h1>", }) ], AppComponent); exports.AppComponent = AppComponent; });
經過 Angular 2 Decorators - part 1 文章,咱們知道 TypeScript 類裝飾器的聲明:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
而轉換後 ES5 代碼中 __decorate 函數的方法簽名是 function (decorators, target, key, desc) 。所以咱們能夠推斷,core_1.Component 是一個函數,該函數調用後返回一個 ClassDecorator 。相似於 Angular 2 Decorators - part 1 文章中的 Greeter 裝飾器:
function Greeter(greeting: string) { return function(target: Function) { target.prototype.greet = function(): void { console.log(greeting); } } } @Greeter('您好') class Greeting { constructor() { // 內部實現 } } let myGreeting = new Greeting(); myGreeting.greet(); // console output: '您好!';
那咱們來看一下 @angular/core 模塊中導出的 Component 函數:
/** * Component decorator and metadata. */ export const Component: ComponentDecorator = <ComponentDecorator>makeDecorator( 'Component', { selector: undefined, // 用於定義組件在HTML代碼中匹配的標籤 inputs: undefined, // 組件的輸入屬性 outputs: undefined, // 組件的輸出屬性 host: undefined, // 綁定宿主的屬性、事件等 exportAs: undefined, // 導出指令,使得能夠在模板中調用 moduleId: undefined, // 包含該組件模塊的id,它被用於解析模板和樣式的相對路徑 providers: undefined, // 設置組件及其子組件能夠用的服務 viewProviders: undefined, // 設置組件及其子組件(不含ContentChildren)能夠用的服務 changeDetection: ChangeDetectionStrategy.Default, // 指定組件使用的變化檢測策略 queries: undefined, // 設置組件的查詢條件 templateUrl: undefined, // 爲組件指定一個外部模板的URL地址 template: undefined, // 爲組件指定一個內聯的模板 styleUrls: undefined, // 爲組件指定一系列用於該組件的樣式表文件 styles: undefined, // 爲組件指定內聯樣式 animations: undefined, // 設置組件相關動畫 encapsulation: undefined, // 設置組件視圖包裝選項 interpolation: undefined, // 設置默認的插值運算符,默認是"{{"和"}}" entryComponents: undefined // 設置須要被提早編譯的組件 }, Directive);
讓咱們繼續來看一下 makeDecorator 這個函數:
// @angular/core/src/util/decorators.ts /** * const Component: ComponentDecorator = <ComponentDecorator>makeDecorator( * 'Component', {...}, Directive); */ function makeDecorator(name, props, parentClass, chainFn) { // name: 'Component', props: {...}, parentClass: Directive if (chainFn === void 0) { chainFn = null; } // 建立Metadata構造函數 var metaCtor = makeMetadataCtor([props]); // objOrType: { selector: 'my-app', template: "<h1>Hello {{name}}</h1>" } function DecoratorFactory(objOrType) { // 確保已經引入了Reflect庫 if (!(Reflect && Reflect.getMetadata)) { throw 'reflect-metadata shim is required when using class decorators'; } // 判斷this對象是否爲DecoratorFactory的實例,如果則合併metadata信息 if (this instanceof DecoratorFactory) { metaCtor.call(this, objOrType); return this; } var annotationInstance = new DecoratorFactory(objOrType); var chainAnnotation = typeof this === 'function' && Array.isArray(this.annotations) ? this.annotations : []; chainAnnotation.push(annotationInstance); // 定義類裝飾器,參數即要裝飾的類 var TypeDecorator = function TypeDecorator(cls) { // 首先先獲取裝飾類關聯的annotations信息,若不存在則建立 // 保存上面建立的annotationInstance實例,並調用保存更新後的annotations信息 var annotations = Reflect.getOwnMetadata('annotations', cls) || []; annotations.push(annotationInstance); Reflect.defineMetadata('annotations', annotations, cls); return cls; }; TypeDecorator.annotations = chainAnnotation; TypeDecorator.Class = Class; if (chainFn) chainFn(TypeDecorator); return TypeDecorator; } if (parentClass) { DecoratorFactory.prototype = Object.create(parentClass.prototype); } DecoratorFactory.prototype.toString = function () { return ("@" + name); }; DecoratorFactory.annotationCls = DecoratorFactory; return DecoratorFactory; } // 生成Metadata構造函數 function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any { // args: [{ selector: 'my-app', template: "<h1>Hello {{name}}</h1>" }] return function ctor(...args: any[]) { props.forEach((prop, i) => { // argVal: { selector: 'my-app', template: "<h1>Hello {{name}}</h1>" } const argVal = args[i]; if (Array.isArray(prop)) { this[prop[0]] = argVal === undefined ? prop[1] : argVal; } else { // propName: 'selector' | 'template' for (const propName in prop) { this[propName] = argVal && argVal.hasOwnProperty(propName) ? argVal[propName] : prop[propName]; } } }); }; }
經過閱讀以上的源碼,咱們發現當調用 makeDecorator('Component', {..}, Directive) 方法時,返回的是
DecoratorFactory 函數,該函數只接收一個參數,當調用該工廠函數時,則返回 TypeDecorator 函數即類裝飾器。回到最先的例子,當咱們調用 core_1.Component({ selector: 'my-app', template: "..." }) 建立的 annotationInstance 實例,內部結構以下:
{ selector: 'my-app', inputs: undefined, outputs: undefined, host: undefined, exportAs: undefined, moduleId: undefined, providers: undefined, viewProviders: undefined, changeDetection: ChangeDetectionStrategy.Default, queries: undefined, templateUrl: undefined, template: "<h1>Hello {{name}}</h1>", styleUrls: undefined, styles: undefined, animations: undefined, encapsulation: undefined, interpolation: undefined, entryComponents: undefined }
如今咱們來梳理一下整個流程,系統初始化的時候,會調用 makeDecorator('Component', {..}, Directive) 方法,建立 ComponentDecorator 工廠 。咱們編寫的 @Component 組件轉換成 ES 5 代碼後,會使用用戶自定義的 metadata 信息做爲參數,自動調用 ComponentDecorator 工廠函數,該函數內部實現的主要功能就是建立 annotationInstance 對象,最後返回 TypeDecorator 類裝飾器。該類裝飾器會被 __decorate([...], AppComponent) 函數調用,參數 traget 就是咱們要裝飾的類 。
由於一個類能夠應用多個裝飾器,因此 var annotations = Reflect.getOwnMetadata('annotations', cls) || [] 語句中,annotations 的值是數組。在 Angular 2 中,應用多個裝飾器的情形是使用 @Optional 、@Inject()、@Host 等參數裝飾器,描述構造函數中須要注入的依賴對象。
經過 Reflect.defineMetadata API 定義的 metadata 信息,是保存在 window['__core-js_shared__'] 對象的 metadata 屬性中。感興趣的話,你們能夠直接在 Console 控制檯,輸入 window['__core-js_shared__'] 查看該對象內部保存的信息。
@Component 中 @ 符號的做用是爲了告訴 TypeScript 編譯器,@ 後面的是裝飾器函數或裝飾器工廠,須要特殊處理。假設在 @Component({...}) 中去掉 @ 符號,那麼變成了普通的函數調用,這樣立刻就會報錯,由於咱們並無定義 Component 函數。經過觀察轉換後的代碼,咱們發現 @Component({...}) 被轉換成 core_1.Component ,它就是從 @angular/core 導入的裝飾器函數。
本文介紹了 Angular 2 中最經常使用的 ComponentDecorator 裝飾器,並經過簡單的例子,一步步分析該裝飾器的內部工做流程。不過咱們只介紹了 Angular 2 框架內部如何解析、建立及保存 metadata 信息,還未涉及到組件初始化的過程當中,如何讀取、應用組件對應的 metadata 信息。另外在後續的 Angular 2 DI 文章中,咱們還會繼續分析其它裝飾器的工做原理。