(更新时间 - 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__'] 对象中,避免对类造成污染。

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

Angular 2 Inject的更多相关文章

  1. 如何使用iOS SDK保存LinkedIn访问令牌?

    我在我的iOS应用程序中使用LinkedIn.我想保存访问令牌以供将来使用.令牌属于非属性类型,无法直接保存在NSUserDefaults中.我尝试使用NSKeyedArchiver,但我得到了输出:令牌中的文本即将到来,但值将为空.代码段1:我也尝试像这样保存,但结果是一样的:代码段2:我的编码或访问令牌有什么问题需要一些特殊技术来保存吗?请建议.解决方法这就是我拯救的方式.它对我有用.希望它有所帮助以这种方式使用保存的responseBody我希望这次我很清楚

  2. Xcode C开发,需要澄清

    我非常喜欢Xcode提供对该语言可能的成员函数的深入了解的方式,并且更喜欢相对于文本伙伴使用它,如果不是因为我今天注意到的奇怪.当strings=“Teststring”时;唯一可用的substr签名如图所示但据我所知,签名应该是什么iseeonline确实s.substr(1,2);既被理解也适用于Xcode.当我尝试方法完成时为什么不显示?

  3. IOS Facebook身份验证使用node.js passport-facebook-token

    我正在尝试使用来自IOS应用程序的passport-facebook-token对node.jsapi进行身份验证.我有用户名和密码验证设置,并通过护照和护照-facebook-token设置正常工作.我只是无法弄清楚将访问令牌发送到API所需的HTTP请求语法.任何帮助都将受到大力赞赏.谢谢.解决方法确定设法从passport-facebook-token的策略文件中找出答案这个需要:http://URL?access_token=[访问令牌]从IOS我只是用以下方法测试:希望这有助于其他人.

  4. 解析条纹iOS main.js

    我真的很难让ParseStripe在我的项目中工作.此时,我想要最简单的工作版本,允许我向用户收费.我找到答案的最接近的事情如下:SimplestExampleI’veFound当我使用上面链接中的更正代码时,我的秘密得到以下错误:请帮助=**(这太令人沮丧了.————-更新—————-其他一些帖子也有类似的错误,看起来最新版本的ParseCloud代码应该归咎于:1.6.0.在控制台视图中使用以

  5. xamarin.ios – 没有找到ViewController ::.ctor(System.IntPtr)的构造函数

    我有一个问题,我的Monotouch应用程序有时在收到内存警告后才会崩溃.请参见下面的堆栈跟踪.堆栈跟踪是正确的,因为指定的类缺少构造函数获取IntPtr参数.但是这是有意的,因为我在应用程序中根本不使用InterfaceBuilder.那为什么会这样呢?

  6. ios – Swift – NSURL错误

    尝试使用下面的NSURL类时出错,下面的代码实际上是试图将我从Facebook拉入的图像存储到imageView中.错误如下:不知道为什么会这样,帮忙!解决方法你正在调用的NSURL构造函数有这个签名:?表示构造函数可能不返回值,因此它被视为可选.NSData构造函数也是如此:快速解决方法是:最好的解决方案是检查(解包)这些选项,即使您确定它们包含值!

  7. 如何在Xcode中追踪“libc abi.dylib:纯虚函数!”

    我有一个多线程OSX应用程序,它使用C,Objective-C和Swift的混合.当我的应用程序关闭时,我在Xcode调试器窗口中看到了这一点:我知道这个错误通常是由对C类构造函数或析构函数中的虚函数的调用引起的.有没有一种简单的方法可以找到它的位置?

  8. Swift实现对象归档

    Swift实现对象归档时有几个注意点要继承NSCoding,实现两个方法extension是一个分类,分类不允许有存储能力,所以协议方法不能写在分类中协议中的init(coderdecoder:NSCoder)函数会覆盖原始的构造函数,所以类中至少还要有另一个init方法如果不指定键名,会使用属性名称作为key,基本数据类型,需要指定key

  9. 【Swift初见】Swift构造过程

    构造过程是通过构造器来实现的,其实每个构造器就可以看作是一个函数,只是这个函数是为了执行初始化的。每个类都必须拥有一个指定构造器。

  10. swift的struct结构体类型介绍使用

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部