Angular 2 Inject

(更新時間 - 2017-03-20 9:00)json

Inject 裝飾器的做用

在 Angular 2 中,Inject 是參數裝飾器,用來在類的構造函數中描述非 Type 類型的參數對象。segmentfault

Angular 2 中 Type 類型:api

// Type類型 - @angular/core/src/type.ts
export const Type = Function;

export function isType(v: any): v is Type<any> {
  return typeof v === 'function';
}

export interface Type<T> extends Function { new (...args: any[]): T; }

Angular 2 中經常使用的非 Type 類型 Token:字符串、OpaqueToken對象、InjectionToken對象等。數組

/*
* 用於建立OpaqueToken實例
* export const CONFIG = new OpaqueToken('config');
*/
export class OpaqueToken {
  constructor(protected _desc: string) {}
  toString(): string { return `Token ${this._desc}`; }
}

/*
* 用於建立InjectionToken實例,使用泛型描述該Token所關聯的依賴對象的類型
* const API_URL = new InjectionToken<string>('apiUrl'); 
*/
export class InjectionToken<T> extends OpaqueToken {
  private _differentiate_from_OpaqueToken_structurally: any;
  constructor(desc: string) { super(desc); }

  toString(): string { return `InjectionToken ${this._desc}`; }
}

(備註:各類 Token 類型的區別,請參照 Angular 2 OpaqueToken & InjectionToken)app

Inject 裝飾器的使用

config.ts函數

export const CONFIG = new OpaqueToken('config');

app.service.tsthis

import { Injectable } from '@angular/core';

@Injectable()
export class AppService {
    constructor() { }
}

app.component.tsspa

import { Component, Inject, ViewChild, HostListener, ElementRef } from '@angular/core';
import { CONFIG } from './config';
import { AppService } from './app.service';

@Component({
  selector: 'my-app',
  template: `<h1 #greet> Hello {{ name }} </h1>`,
})
export class AppComponent {
  name = 'Angular';

  @ViewChild('greet')
  private greetDiv: ElementRef;

  @HostListener('click', ['$event'])
  onClick($event: any) {
    console.dir($event);
  }

  constructor(public appService: AppService,
    @Inject(CONFIG) config: any) {
  }
}

編譯後的 ES5 代碼片斷:code

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {...};
var __metadata = (this && this.__metadata) || function (k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
  return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
   return function (target, key) { decorator(target, key, paramIndex); }
};
  
var AppComponent = (function () {
      // 構造函數
    function AppComponent(appService, config) {
        this.appService = appService;
        this.name = 'Angular';
    }
  
    AppComponent = __decorate([
        core_1.Component({ // 調用ComponentDecoratorFactory返回TypeDecorator
            selector: 'my-app',
            template: "<h1 #greet> Hello {{ name }} </h1>",
        }),
        // 調用ParamDecoratorFactory返回ParamDecorator
        __param(1, core_1.Inject(config_1.CONFIG)), 
        // 保存構造函數參數的類型
        __metadata('design:paramtypes', [app_service_1.AppService, Object])
    ], AppComponent);
    return AppComponent;
}());
exports.AppComponent = AppComponent;

Inject 裝飾器實現

Inject、InjectDecorator 接口及 Inject 函數:component

// Inject接口定義
export interface Inject { token: any; }

// InjectDecorator接口定義
export interface InjectDecorator {
  (token: any): any;
  new (token: any): Inject; // 構造函數的簽名
}

// Inject裝飾器:即示例中轉成ES5代碼後的 core_1.Inject 對象 - core_1.Inject(config_1.CONFIG)
export const Inject: InjectDecorator = makeParamDecorator('Inject', [['token', undefined]]);

makeParamDecorator函數片斷:

/*
 * 建立ParamDecorator工廠
 *
 * 調用 makeParamDecorator('Inject', [['token', undefined]])後返回ParamDecoratorFactory
 */
function makeParamDecorator(name, props, parentClass) {
          // name: 'Inject', props: [['token', undefined]]
          // 建立Metadata構造函數
        var metaCtor = makeMetadataCtor(props);

         // __param(1, core_1.Inject(config_1.CONFIG))
        function ParamDecoratorFactory() {
          // 解析參數並建立annotationInstance實例
            var args = [];
           // arguments: {0: CONFIG}
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i - 0] = arguments[_i];
            }
            if (this instanceof ParamDecoratorFactory) {
                // args: [CONFIG]
                metaCtor.apply(this, args);
                return this;
            }
            ... 
            return ParamDecorator;
      
            function ParamDecorator(cls, unusedKey, index) {
               // 獲取類已經定義的metadata信息 
                var parameters = Reflect.getOwnMetadata('parameters', cls) || [];
                while (parameters.length <= index) {
                    parameters.push(null);
                }
                // parameters是一個二維數組,由於支持同時應用多個裝飾器
                // eg:  @Inject(CONFIG) @Optional() @SkipSelf() config: any
                parameters[index] = parameters[index] || [];
                parameters[index].push(annotationInstance);
                Reflect.defineMetadata('parameters', parameters, cls);
                return cls;
            }
            var _a;
        }
        return ParamDecoratorFactory;
}

makeMetadataCtor 函數:

// 生成Metadata構造函數: var metaCtor = makeMetadataCtor(props); 
// props: [['token', undefined]]
  function makeMetadataCtor(props) {
        return function ctor() {
          /*
          * metaCtor.apply(this, args);
          */ 
            var _this = this;
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i - 0] = arguments[_i];
            }
            props.forEach(function (prop, i) { // prop: ['token', undefined]
                var argVal = args[i]; 
                if (Array.isArray(prop)) { // prop: ['token', undefined]
                      // prop[0]: token, argVal: CONFIG - {_desc: "config"}
                    _this[prop[0]] = argVal === undefined ? prop[1] : argVal;
                }
                else {
                    for (var propName in prop) {
                        _this[propName] =
                            argVal && argVal.hasOwnProperty(propName) ? 
                          argVal[propName] : prop[propName];
                    }
                }
            });
        };
}

接下來咱們能夠在控制檯輸入 window['__core-js_shared__'] ,查看經過 Reflect API 保存後的metadata信息

圖片描述

最後咱們來了解一下,Angular 如何獲取 AppComponent 構造函數中,經過 @Inject 裝飾器設置的 metadata信息。

// @angular/core/src/reflection/reflection_capabilities.ts
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
  // 獲取ParamDecorator函數中經過Reflect.defineMetadata('parameters', parameters, cls)
  // 保存的metadata信息
   parameters(type: Type<any>): any[][] {
    if (!isType(type)) { return []; }
    const parentCtor = getParentCtor(type);
    let parameters = this._ownParameters(type, parentCtor);
    if (!parameters && parentCtor !== Object) {
      parameters = this.parameters(parentCtor);
    }
    return parameters || [];
  }
}

private _ownParameters(type: Type<any>, parentCtor: any): any[][] {
  /* 
   * constructor(
   *  public appService: AppService,
   *  @Inject(CONFIG) config: any) {
   * }
   */
   if (this._reflect != null && this._reflect.getOwnMetadata != null) {
     // @Inject(CONFIG) config: any -> 'parameters'
      const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
     // appService: AppService -> 'design:paramtypes'
      const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
      if (paramTypes || paramAnnotations) {
        return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
      }
    }
}

我有話說

1.爲何在構造函數中,非 Type 類型的參數只能用 @Inject(Something) 的方式注入 ?

由於只有是 Type 類型的對象,纔會被 TypeScript 編譯器編譯。具體參照下圖:

圖片描述

2.爲何 TypeScript 會自動保存 metadata 信息 ?

由於咱們在 tsconfig.json 文件中,進行以下配置:

{
  "compilerOptions": {
      ...,
    "emitDecoratorMetadata": true
    }
 }

3.AppService 中 @Injectable() 是必須的麼 ?

若是 AppService 不依賴於其餘對象,是能夠不用使用 Injectable 類裝飾器。當 AppService 須要在構造函數中注入依賴對象,就須要使用 Injectable 類裝飾器。比較推薦的作法無論是否有依賴對象,service 中都使用 Injectable 類裝飾器。

4.Reflect 對象還有哪些方法 ?

Reflect
  .defineMetadata(metadataKey, metadataValue, target, propertyKey?) -> void
  .getMetadata(metadataKey, target, propertyKey?) -> var
  .getOwnMetadata(metadataKey, target, propertyKey?) -> var
  .hasMetadata(metadataKey, target, propertyKey?) -> bool
  .hasOwnMetadata(metadataKey, target, propertyKey?) -> bool
  .deleteMetadata(metadataKey, target, propertyKey?) -> bool
  .getMetadataKeys(target, propertyKey?) -> array
  .getOwnMetadataKeys(target, propertyKey?) -> array
  .metadata(metadataKey, metadataValue) -> decorator(target, targetKey?) -> void

Reflect API 使用示例

var O = {};
Reflect.defineMetadata('foo', 'bar', O);
Reflect.ownKeys(O);               // => []
Reflect.getOwnMetadataKeys(O);    // => ['foo']
Reflect.getOwnMetadata('foo', O); // => 'bar'

5.使用 Reflect API 有什麼好處 ?

  • 使用 Reflect API 咱們可以方便的對類相關的 metadata 信息進行保存和讀取

  • Reflect API 把類相關的 metadata 信息保存在 window['__core-js_shared__'] 對象中,避免對類形成污染。

6.在構造函數中,Type 類型的參數能用 @Inject(Type) 的方式注入麼?

Type 類型的參數也能使用 @Inject(Type) 的方式注入,具體以下:

constructor(@Inject(Http) private http) { }

一樣也能夠使用如下方式:

constructor(@Inject(Http) private http: Http) { }

第一種方式雖然能夠正常編譯,但 IDE 會有以下的提示信息:

[ts] Parameter 'http' implicitly has an 'any' type.

第二種方式,雖然 Angular 內部會合並 design:paramtypes 與 parameters 內的 metadata 信息,但本人以爲是有點冗餘了。 總而言之,若果是 Type 類型的參數,推薦使用下面的方式:

constructor(private http: Http) { }

總結

本文經過一個示例,一步步分析 Inject 裝飾器的做用及內部實現原理,此外咱們還解釋了在構造函數中爲何非 Type 類型的參數只能經過 @Inject(Something) 的方式注入及 Injectable裝飾器的使用場景,最後咱們還簡單介紹了Reflect API。但願經過這篇文章,能讓讀者更好地理解 Inject 裝飾器。

相關文章
相關標籤/搜索