原文地址:http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html
如果你关注我们的文章 Angular2中的依赖注入,你知道DI系统在Angular中是如果运作的,它利用在我们代码上通过注解添加Metadata来获取所有关于依赖的信息来解决我们的依赖关系
Angular 2 应用基本上可以用任何语言编写。只要它以某种方式编译成JavaScript,当我们使用Typescript编写我们应用时,我们使用decorator来给我们代码添加Metadata,有时,我们甚至忽略一些decorator,单纯依靠类型注释。然而,事实证明,当涉及到DI,我们可能注入依赖到服务时遇到意外的行为。
本文讨论了这个意外的问题,为什么它存在,以及如何解决。
注入服务依赖
比方说我们有一个简单的Angular 2 组件有一个DataService依赖,它可能是这个样子:
@Component({ selector: 'my-app' }) @View({ directives: [NgFor],template: ` <ul> <li *ng-for="#item in items">{{item.name}}</li> </ul> ` }) class AppComponent { items:Array<any>; constructor(dataService: DataService) { this.items = dataService.getItems(); } }
另一方面 DataService 是一个简单的类(因为它在Angualr2是一个服务),它提供了一个方法返回一些items
class DataService {
items:Array<any>;
constructor() {
this.items = [
{ name: 'Christoph Burgdorf' },{ name: 'Pascal Precht' },{ name: 'thoughtram' }
];
}
getItems() {
return this.items;
}
}
当然,为了能使用DataService
类型,我们必须为injector
添加这个provider
,当引导我们的应用时可以这样做,通过传递一个provider
给boostrap()
;
bootstrap(AppComponent,[DataService]);
到现在为止没有什么新的内容,如果这对你来说是新内容,你可能需要先阅读我们的Angular2中的依赖注入文章。
那么问题在那里呢? 这个问题发生在当我们试图注入一个依赖进我们的服务,比如,我们可以使用Http
在我们的DataService
里从远程服务器获取数据,让我们快速实现这个。首先,我们需要为injector
提供一个provider
,让DataService
知道有关http
。
import {HTTP_PROVIDERS} from 'angular2/http';
...
bootstrap(AppComponent,[HTTP_PROVIDERS,DataService]);
Angular http
模块 暴露了 HTTP_PROVIDERS
,它包含了所有的我们需要用到的http
操作的provider
,接下来,我们需要在我们的服务中注入这个实例
import {Http} from 'angular2/http';
class DataService {
items:Array<any>;
constructor(http:Http) {
...
}
...
}
轰. 这个东西会爆炸。当我们在浏览器中运行这段代码,我们会得到以下错误:
Cannot resolve all parameters for DataService(?). Make sure they all have valid type or annotations.
这错误基本上的意思是,它不能解决DataService
的Http
依赖,因为Anuglar不知道该类型,因此 没有provider
可以用来解决该依赖,恩。。等等,我们没有将变量类型提供给了constructor
吗?
不,我们提供了,不幸的是,这是不够的,但是 在我们的AppComponent
我们注入的DataService
显然是在正常工作,那在这里有什么问题?
在我们的Annotation和Decorator之间的区别文章,我们获悉,decorator
只是简单的为我们的代码添加Metadata
,我们来看下编译后的AppComponent
的decorator
function AppComponent(myService) {
...
}
AppComponent = __decorate([
Component({...}),View({...}),__Metadata('design:paramtypes',[DataService])
],AppComponent);
我们可以清楚地看到,AppComponent
类会被__decorate
函数给包装,里面包装了Component
,View
和paramtypes
的Metadata
,
paramtypes
元素是一个告诉angular DI用来弄清楚,它必须返回一个什么类型的实例
这看起来很不错。让我们来看看被编译后的DataService
,看看在那里发生了什么事情(也简化了)。
DataService = (function () {
function DataService(http) {
...
}
return DataService;
})();
哎呀。显然,在这里没有任何Metadata
。这是为什么?
当设置了emitDecoratorMetadata
选项,TypeScript会生成Metadata
,然而,这并不意味着它会盲目的为我们代码的每个类或方法生成Metadata
,TypeScript只会对那些被decorator
附加的类,方法,属性或者构造函数参数来生成相应的Metadata
,否则,会产生大量的未使用的Metadata
代码,这不仅影响文件的大小,也对我们的应用程序运行产生影响。
这就是为什么AppComponent
会生成Metadata
,而DataService
不生成,我们的AppComponent
有一个decorator
强制生成Metadata
那么我们如果才能强制TypeScript为我们生成Metadata
呢,我们可以做的一件事,就是用框架提供的Di decorator,正如我们在DI的其他文章中了解到,@Inject
decorator
用来要求某种类型的依赖
我们可以改变我们的DataService
的成这样的:
import {Inject} from 'angular2/core';
import {Http} from 'angular2/http';
class DataService {
items:Array<any>;
constructor(@Inject(Http) http:Http) {
...
}
...
}
问题解决。事实上,如果查看经过编译后的代码时会发现已生成需要的Metadata
function DataService(http) { } DataService = __decorate([ __param(0,angular2_1.Inject(Http)),__Metadata('design:paramtypes',[Http]) ],DataService);
我们基本上可以在我们代码上做任何decorator
,只要在class
,或者构造函数参数上附加任何decorator
,换一种说法,我们可以把 @Inject
移除,然后在这个类上使用别的decorator
,因为这将导致TypeScript为构造函数参数生成Metadata
当然。在一个类上使用一个decorator
,来解决所有的问题,听起来不是很合适。幸运的是,我们可以使用Angular自带的另一个decorator
。@Injectable
是一个用于Dart的Metadata
创建,在TypeScript,它没有任何特殊含义,然而,事实证明是非常适合我们的用例.
我们所要做的就是导入它,把它放在我们的DataService
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
@Injectable()
class DataService {
items:Array<any>;
constructor(http:Http) {
...
}
...
}
同样,它只是强制TypeScript发射需要的matadata
,这个decorator
在这里并没有什么特殊含义,这似乎是我们目前解决所示问题的最佳选择