Angular 2 Inject

(更新时间 - 2017-03-20 9:00)

Inject 装饰器的作用

在 Angular 2 中,Inject 是参数装饰器,用来在类的构造函数中描述非 Type 类型的参数对象。

Angular 2 中 Type 类型:

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

Inject 装饰器的使用

config.ts

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

app.service.ts

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

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

app.component.ts

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 代码片段:

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,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 函数

// 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',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',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,propertyKey?) -> void
  .getMetadata(MetadataKey,propertyKey?) -> var
  .getOwnMetadata(MetadataKey,propertyKey?) -> var
  .hasMetadata(MetadataKey,propertyKey?) -> bool
  .hasOwnMetadata(MetadataKey,propertyKey?) -> bool
  .deleteMetadata(MetadataKey,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__'] 对象中,避免对类造成污染。

  • @H_502_99@

    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 装饰器。

相关文章

AngularJS 是一个JavaScript 框架。它可通过 注:建议把脚本放在 元素的底部。这会提高网页加载速度,因...
angluarjs中页面初始化的时候会出现语法{{}}在页面中问题,也即是页面闪烁问题。出现这个的原因是:由于...
AngularJS 通过被称为指令的新属性来扩展 HTML。AngularJS 指令AngularJS 指令是扩展的 HTML 属性,带有...
AngularJS 使用表达式把数据绑定到 HTML。AngularJS 表达式AngularJS 表达式写在双大括号内:{{ expres...
ng-repeat 指令可以完美的显示表格。在表格中显示数据 {{ x.Name }} {{ x.Country }} 使用 CSS 样式为了...
$http是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。读取 JSON 文件下是存储在web服务器上...