如果你關注我們的文章 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"></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在這裏並沒有什麼特殊含義,這似乎是我們目前解決所示問題的最佳選擇