Angular2 依賴注入

1. 使用DI

依賴注入是一個很重要的程序設計模式。 Angular 有本身的依賴注入框架,離開了它,咱們幾乎無法構建 Angular 應用。它使用得很是普遍,以致於幾乎每一個人都會把它簡稱爲 DI。設計模式

咱們來看一個簡單的例子:數組

export class Animal {網絡

dogs;app

constructor() {框架

var dog = new Dog();ide

}函數

}測試

咱們的Animal在構造函數中手工建立所需的每樣東西。問題在於,咱們這個 Animal類過於脆弱、缺少彈性而且難以測試。網站

當咱們的Animal 類須要一個 Dog,沒有去請求一個現成的實例, 而是在構造函數中用具體的 Dog類新建立了一份只供本身用的副本。this

若是 Dog類升級了,而且它的構造函數要求傳入一個參數了,該怎麼辦? 咱們這個Animal類就被破壞了,並且直到咱們把建立引擎的代碼重寫爲 Dog= new Dog(theNewParameter) 以前,它都是壞的。可是當Dog類的定義發生變化時,咱們就不得不在意了,Animal類也不得不跟着改變。 這就會讓Animal類過於脆弱。

如今,每一個Animal都有本身獨特的Dog。他沒法被其餘Animal共享。咱們的Animal缺少必要的彈性,沒法共享給其餘的Animal類消費。

咱們該如何讓 Animal更強壯、有彈性以及可測試?

答案超級簡單。咱們把Animal的構造函數改形成使用 DI 的版本:

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

發生了什麼?咱們把依賴的定義移到了構造函數中。 咱們的Animal類再也不建立Dog, 它僅僅「消費」它們。若是有人擴展了Dog類,那就再也不是Animal類的煩惱了。

2. Angular DI

Angular 自帶了它本身的依賴注入框架。此框架也能被當作獨立模塊用於其它應用和框架中。

2.1 注入器樹

Angular 有一個多級依賴注入系統。實際上,應用程序中有一個與組件樹平行的注入器樹,咱們能夠在組件樹中的任何級別上從新配置注入器。常見的注入器數的形式以下:

x

當一個底層的組件申請得到一個依賴時, Angular 先嚐試用該組件本身的注入器來知足它。 若是該組件的注入器沒有找到對應的提供商,它就把這個申請轉給它父組件的注入器來處理。 若是那個注入器也沒法知足這個申請,它就繼續轉給 它的父組件的注入器。 這個申請繼續往上冒泡——直到咱們找到了一個能處理此申請的注入器或者超出了組件樹中的祖先位置爲止。 若是超出了組件樹中的祖先還未找到, Angular 就會拋出一個錯誤。

2.2 實現原理

Angular給依賴注入器提供令牌來獲取服務。一般在構造函數裏面,爲參數指定類型,讓 Angular 來處理依賴注入。該參數類型就是依賴注入器所需的 令牌 。 Angular 把該令牌傳給注入器,而後把獲得的結果賦給參數。

注入器從哪兒獲得的依賴? 它可能在本身內部容器裏已經有該依賴了。 若是它沒有,也能在 提供商 的幫助下新建一個。 提供商 就是一個用於交付服務的配方,它被關聯到一個令牌。Angular會根據該令牌根據供應商建立一個服務結果返回,並將其保存在注入器內部供之後調用。

2.3 令牌

當咱們爲注入器註冊一個提供商時,其實是把這個提供商和一個 DI 令牌關聯起來了。 注入器維護一個內部的 令牌 - 提供商 映射表,這個映射表會在請求一個依賴時被引用到。令牌就是這個映射表中的鍵值 key 。

2.3.1 類依賴

通常狀況下,依賴值都是一個類 實例 ,而且類的類型是它本身的查找鍵值。 這種狀況下,咱們其實是直接從注入器中以 類型做爲令牌,來獲取一個 實例。

寫一個須要基於類的依賴注入的構造函數對咱們來講是很幸運的。咱們只要以 類爲類型,定義一個構造函數參數, Angular 就會知道把跟 類令牌關聯的服務注入進來,大多數依賴值都是以類的形式提供的。

例如,其中Animal依賴Dog類,在構造函數中提供Dog類型,就能夠依賴注入對應的Dog類實例。

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

請注意,TypeScript 接口不是一個有效的令牌。

2.3.2 非類依賴

若是依賴值不是一個類呢?有時候咱們想要注入的東西是一個字符串,函數或者對象。

應用程序常常爲不少很小的因素 ( 好比應用程序的標題,或者一個網絡 API 終點的地址 ) 定義配置對象,可是這些配置對象不老是類的實例。

可是這種狀況下咱們要把什麼用做令牌呢? 解決方案是定義和使用一個 OpaqueToken。定義方式相似於這樣:

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

export let APP_CONFIG = new OpaqueToken('app.config');

咱們使用這個 OpaqueToken 對象註冊依賴的提供商:

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

如今,在 @Inject 的幫助下,咱們能夠把這個配置對象注入到任何須要它的構造函數中:

constructor(@Inject(APP_CONFIG) config: AppConfig) {

this.title = config.title;

}

雖然 ConfigAppConfig 接口在依賴注入過程當中沒有任何做用,但它爲該類中的配置對象提供了強類型信息。

2.4 提供商

提供商 提供 依賴注入的一個運行時版本。 注入器依靠 提供商們 來建立服務的實例,它會被注入器注入到組件或其它服務中。

Angular中使用provide對象來做爲提供商,該 provide 對象須要一個令牌 和一個定義對象,該令牌一般是一個類,但並不是必定是。

2.4.1 userValue-值提供商

該定義對象有一個主屬性 ( 即userValue) ,用來標識該提供商會如何新建和返回依賴。

把一個固定的值 ,也就是該提供商能夠將其做爲依賴對象返回的值,賦給 userValue 屬性。

一般使用該技巧來進行運行期常量設置 ,好比網站的基礎地址和功能標誌等。 咱們在OpaqueToken中已經見識了一個例子,咱們爲APP_CONFIG提供了一個常量做爲定義對象。

{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }

一個值提供商的值必需要當即定義。不能過後再定義它的值。很顯然,標題字符串是馬上可用的。

2.4.2 useClass -類提供商

userClass 提供商建立並返回一個指定類的新實例。使用該技術來爲公共或默認類 提供備選實現。通常來講,被新建的類同時也是該提供商的注入令牌,例如一個提供日誌服務的提供商

{ provide: LoggerService, useClass:LoggerService }

咱們在依賴注入LoggerService時,會根據類LoggerService來建立一個默認的示例做爲結果返回。

當依賴注入的類有其餘類型依賴的狀況下,例如LoggerService依賴於用戶信息,咱們一樣用構造函數注入模式,來添加一個帶有 LoggerService參數的構造函數。這種狀況下就須要用到Injectable註解。

@Injectable() 標誌着一個類能夠被一個注入器實例化。當咱們的LoggerService服務有了一個注入的依賴,咱們須要使用@Injectable()來標識LoggerService,這樣Angular 就可使用構造函數參數的元數據來注入一個 用戶服務

2.4.3 useExisting - 別名提供商

useExisting ,提供商能夠把一個令牌映射到另外一個令牌上。實際上,第一個令牌是第二個令牌所對應的服務的一個別名,創造了訪問同一個服務對象的兩種方法 。

{ provide: MinimalLogger, useExisting:LoggerService }

經過使用別名接口來把一個 API 變窄,是一個很重要的該技巧的使用例子。咱們在這裏就是爲了這個目的使用的別名。 想象一下若是 LoggerService 有個很大的 API 接口 ( 雖然它其實只有三個方法,一個屬性 ) ,經過使用 MinimalLogger 類 - 接口別名,就能成功的把這個 API 接口縮小到只暴露兩個成員:

export abstract class MinimalLogger {

logInfo: (msg: string) => void;

logs: string[];

}

2.4.4 useFactory工廠提供商

useFactory 提供商經過調用工廠函數來新建一個依賴對象,主要用來建立一個擁有參數的對象來做爲提供者。

使用這項技術,能夠用包含了一些 依賴服務和本地狀態 輸入的工廠函數來 創建一個依賴對象,該依賴對象不必定是一個類實例,它能夠是任何東西。例如

export function factory(level){

return new Logger(level)

}

{ provide: RUNNERS_UP, useFactory: factory, deps: [2] }

2.5 配置注入器。

通常來講輸入器的位置有兩種,一種是在NgModule中注入,一種是在Component中注入。兩種類型都是在元數據中的providers數組中注入,區別在在生效的範圍不一樣,Component中注入只在當前組件以及子組件中生效。例如

Providers:[

UserService,

{ provide: Logger, useClass: EvenBetterLogger }

]

其中UserService便是類提供商的簡寫

{ provide: UserService, useClass: UserService}

2.6 使用DI

咱們知道了如何配置服務提供商,如今咱們來了解一下如何使用。

2.6.1 構造函數

一般狀況下咱們使用構造函數參數來注入對應的服務。通常來說主要存在兩種狀況。

首先,是類型做爲令牌的依賴注入,這種狀況下,能夠直接使用構造函數中的參數類型進行依賴注入,例如在Animal中使用Dog類型

export class Animal {

dogs;

constructor(private dog:Dog) {

}

}

其次,可使用@Inject(‘token’)的形式注入令牌的類型的對象或者服務,例如咱們注入值類型的配置對象

constructor(@Inject(APP_CONFIG) config: AppConfig) {

this.title = config.title;

}

2.6.2 獲取父組件

一般來講獲取父組件就是獲取一個已經存在的組件類型,父組件必須經過提供一個與別名提供者來實現,例如

providers: [{provide: Parent, useExisting: forwardRef(() => ParentComponent) }]

Parent 是該提供商的令牌, ParentComponent就是該別名提供商的類型,將該類型提供商注入到父組件的注入器中,則子組件可使用Parent令牌做爲構造函數參數類型來注入該服務,獲取ParentComponent。 ParentComponent引用了自身,形成循環引用,必須使用 前向引用forwardRef 打破了該循環,查找當前或者父級的提供商。

2.6.3 跳過自身與可選

當咱們不想從當前元素獲取依賴的時候,可使用@SkipSelf(),這樣注入器從一個在本身 上一級 的組件開始搜索一個 Parent 依賴。同時,當沒法確保依賴是否存在的狀況下,而又爲了不拋出找不到依賴的錯誤狀況,可使用@Optional()註解,這樣該依賴是可選的,例如引入父組件的構造函數能夠寫成以下的格式:

constructor( @SkipSelf() @Optional() public parent: Parent ) { }

相關文章
相關標籤/搜索