您好,登錄后才能下訂單哦!
這篇文章主要介紹了Angular中依賴注入怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
一、什么是依賴注入
控制反轉(IoC)
控制反轉的概念最早在2004年由Martin Fowler提出,是針對面向對象設計不斷復雜化而提出的一種設計原則,是利用面向對象編程法則來降低應用耦合的設計模式。
IoC強調的是對代碼引用的控制權由調用方轉移到了外部容器,在運行是通過某種方式注入進來,實現了控制反轉,這大大降低了程序之間的耦合度。依賴注入是最常用的一種實現IoC的方式,另一種是依賴查找。
依賴注入(Dependency Injection)
當然,按照慣例我們應該舉個例子, 哦對,我們主要說明的是依賴注入,依賴查找請自行查閱資料。
假設我們有一個能做漢堡的設備(HRobot),需要用肉(meat)和一些沙拉(salad)作為原料,我們可以這樣實現:
export class HRobot { public meat: Meat; public salad: Salad; constructor() { this.meat = new Meat(); this.salad = new Salad(); } cook() {} }
看一下好像沒有什么問題,可能你已經發現,我們的原材料都是放在機器里面的,如果我們想吃別的口味的漢堡恐怕就要去鄉村基了。
為了可以吃到別的口味的漢堡,我們不得不改造一下我們的HRobot
:
export class HRobot { public meat: Meat; public salad: Salad; constructor(public meat: Meat, public salad: Salad) { this.meat = meat; this.salad = salad; } cook() {} }
現在,只要要直接給它meat和salad就好了,我們的HRobot()并不需要知道給它的是什么樣的meat:
let hRobot = new HRobot(new Meat(), new Salad());
比如,我們想吃雞肉漢堡,只需要個它一塊雞肉就好:
class Chicken extends Meat { meat = 'chiken'; } let cRobot = new HRobot(new Chicken(), new Salad());
感覺還不錯,我們再也不會為了吃一個雞肉漢堡大費周章的去改造一臺機器,這太不可思議了。
我可能想到了,你還是懶得弄塊雞肉給它,這時候可以使用工廠函數:
export class HRobotFactory { createHRobot() { let robot = new HRobot(this.createMeat(), this.createSalad()); } createMeat() { return new Meat(); } creatSalad() { return new Salad(); } }
現在有了工廠,就有源源不斷的漢堡可以吃了,開不開心,驚不驚喜?
好吧,沒有最懶,只有更懶,連工廠都懶得管理我也是無話可說,幸運的是我們有Angular提供的依賴注入框架,它可以讓你伸手就有漢堡吃!
二、 Angular依賴注入
在介紹Angular依賴注入之前,先來理一下三個概念:
注入器(Injector):就想制造工廠,提供了一系列的接口,用于創建依賴對象的實例。
提供商(Provider):用于配置注入器,注入器通過它來創建被依賴對象的實例,Provider把令牌(Token)映射到工廠方法,被依賴的對象就是通過這個方法創建的。
依賴(Denpendence):指定了被依賴對象的類型,注入器會根據此類型創建對應的對象。
說了半天到底是什么樣的?
用代碼示例如下:
var injector = new Injector(...); var robot = injector.get(HRobot); robot.cook();
Injector()的實現如下:
import { ReflecttiveInjector } form '@angular/core'; var injector = ReflectiveInjector.resolveAndCreat([ {provide: HRobot, useClass: HRobot}, {provide: Meat, useClass: Meat}, {provide: Salad, useClass: Salad} ]);
還有注入器是這樣知道知道初始化HRobot需要依賴Meat和Salad:
export class Robot { //... consructor(public meat: Meat, public salad: Salad) {} //... }
當然,看了頭大是應該的,因為上面的東西壓根就不需要自己動手寫,Angular的依賴注入框架已經自動幫我們完成了(注入器的生成和調用)。
1. 在組件中注入服務
Angular在底層做了大量的初始化工作,這極大地降低了我們使用依賴注入的成本,現在要完成依賴注入,我們只需要三步:
通過import導入被依賴的對象服務
在組件中配置注入器。在啟動組件時,Angular會讀取@Component裝飾器里的providers元數據,它是一個數組,配置了該組件需要使用的所有依賴,Angular的依賴注入框架會根據這個列表去創建對應的示例。
在組件構造函數中聲明需要注入的依賴。注入器會根據構造函數上的聲明,在組件初始化時通過第二步中的providers元數據配置依賴,為構造函數提供對應的依賴服務,最終完成依賴注入。
例子來了:
// app.component.ts //... // 1. 導入被依賴對象的服務 import { MyService } from './my-service/my-service.service'; @Component({ //... // 2. 在組件中配置注入器 providers: [ MyService ] //... }) export class AppComponent { // 3. 在構造函數中聲明需要注入的依賴 constructor(private myService: MyService) {} }
2. 在服務中注入服務
除了組件依賴服務,服務間依的相互調用也很寒常見。例如我們想給我們的漢堡機器人加上一個計數器,來記錄它的生產狀況,但是計數器又依靠電源來工作,我們就可以用一個服務來實現:
// power.service.ts import { Injectable } from '@angular/core'; @Injectable() export class PowerService { // power come from here.. } // count.service.ts import { Injectable } from '@angular/core'; import { PowerService } from './power/power.service'; @Injectable() export class CountService { constructor(private power: PoowerService) {} } // app.component.ts 這里是當前組件,其實模塊中的注入也一樣,后面講到 //... providers: [ CountService, PowerService ]
這里需要注意的是@Injectable
裝飾器是非必須的,因為只有一個服務依賴其他服務的時候才必須需要使用@Injectable
顯式裝飾,來表示這個服務需要依賴,所以我們的PowerService并不是必須加上@Injectable
裝飾器的,可是,Angular官方推薦是否依賴其他服務,都應該使用@Injectable
來裝飾服務。
3. 在模塊中注入服務
在模塊中注冊服務和在組件中注冊服務的方法是一樣的,只是在模塊中注入的服務在整個組件中都是可用的。
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule ], providers: [CountService, PowerService], bootstrap: [AppComponent] }) export class AppModule { }
與在組件中注入不同的是,在Angular應用啟動的時候,它好首先加載這個模塊需要的所有依賴,,此時會生成一個全局的根注入器,由該依賴創建的依賴注入對象會再整個應用中可見,并共享一個實例。
Angular沒有模塊級作用域這個概念,只有應用程序級作用域和組件級作用域,這種設計主要是考慮模塊的擴展性,一個應用通常由多個模塊合并和成,在@NgModule中注冊的服務,默認在整個應用中可用。
下面說兩種特殊情況:
假設在兩個模塊中使用同樣的Token注入了同一個服務,并且這兩個模塊先后導入到了根組件中:
// ... @NgModule({ imports: [ AModule, BModule ] // ... })
那么后面導入的模塊中的服務會覆蓋前面導入模塊中的服務,也就是說BModule中的服務會覆蓋AModule中的服務,即使是在AModule中注入的服務,同樣使用的是BMoudle中提供的實例。
還是假設兩個模塊同樣使用同一個Token注入了同一個服務,但是BModule模塊是導入在AModule模塊中的:
// a.module.ts // ... @NgModule({ imports: [BModule] })
那么這種情況下兩個模塊使用的都是AModule中注入的服務。可以推斷出在根模塊中注入的服務是擁有最高優先級的,你可以在任何地方放心使用。
三、Provider
1. Provider的理解
Provider是有必要單獨提出來一節的,上面第二節中我們其實只是簡單的使用了其中一種的provider下面來詳細說一下Provider
在Angular中,Provider描述了注入器(Injector)如何初始化令牌(Token)所對應的依賴服務。Provider一個運行時的依賴,注入器依靠它來創建服務對象的實例。
比如我們上面用到的例子:
// ... @Component({ //... // 2. 在組件中配置注入器 providers: [ MyService ] //... })
實際上它的完整形式應該是這樣的:
@Component({ //... // 2. 在組件中配置注入器 providers: [ {provide: MyService, useClass: MyService} ] //... })
所以說我們上面只使用了一種provider: 類Provider(ClassProvider)。
2. Provider注冊方式
上面提到我只使用了其中一種注冊方式,那么下面介紹Angular中提供的四中常見的注冊方式:
類Provider(ClassProvider)
值Provider(ValueProvider)
別名Provider(ExistingProvider)
工廠Provider(FactoryProvider)
1. 類Provider
類Provider 基于令牌(Token)指定依賴項,這種方式可是讓依賴被動態指定為其他不同的具體實現,只要接口不變,對于使用方就是透明的。比如數據渲染服務(Render),Render服務對上層提供的接口是固定的,倒是底層的渲染方式可以不同:
```ts var inject = Injector.resolveAndCreate([ {provide: Render, useClass: DomRender} //{provide: Render, useClass: DomRender} // canvas 渲染方式 //{provide: Render, useClass: DomRender} // 服務的想染方式 ]) // 調用方不用做任何修改 class AppComponent { construtor(private render: Render) {} } ```
2. 值Provider
由于依賴的對象并不一定都是類,也可以是字符串、常量、對象等其他數據類型的,這可以方便用在全局變量、系統相關參數配置場景中。在創建Provider
對象的時候,只需要使用useValue就可以聲明一個值Provider
:
```ts let freeMan = { freeJob: boolen; live: () => {return 'do something u cant do'} }; @Component({ // ... providers: [ {provide: 'someone', useValue: freeMan} ] }) ```
3. 別名Provider
有了別名Provider
,我們就可以在一個Provider
中配置多個令牌(Token
),其對于的對象指向同一個實例,從而實現了多個依賴、一個對象實例的作用:
// ... providers: [ {provider: Power1, useClass: PowerService}, {provider: Power2, useClass: PowerService} ] // ...
仔細想想,這樣對嗎?
顯然是不對的,如果兩個都使用了useClass那么按照令牌,將會創建兩個不同的實例出來,那么應該怎么實現兩個令牌同一個實例呢?答案是使用useExistiong:
// ... providers: [ {provider: Power1, useClass: PowerService}, {provider: Power2, useExisting: PowerService} ] // ...
4. 工廠Provider
工廠Provider
允許我們根據不同的條件來實例化不同的服務,比如,我們在開發環境需要打印日志,但是在實際部署的時候可能并不需要打印這些東西,那么我們總不可能去找到整個應用中所有的console.log()
這樣的方法吧,這個時候我們可以使用工廠provider
來幫我們處理,我們只需要在工廠provider
中設定一個條件,使其能夠根據條件返回實例化我們需要的服務就可以了。為了實現這樣的功能我們可以在根模塊中這樣注入:
```ts // app.module.ts @NgModule({ // ... providers: [ HeroService, ConsoleService, { provide: LoggerService, useFactory: (consoleService) => { return new LoggerService(true, consoleService); }, deps: [ConsoleService] } ], bootstrap: [AppComponent] }) export class AppModule { }
哦哦,那兩個服務是這樣寫的:
// console.service.ts // ... export class ConsoleService { log(message) { console.log(`ConsoleService: ${message}`); } } // logger.service.ts // ... export class LoggerService { constructor(private enable: boolean, consoleService: ConsoleService ) { } log(message: string) { if (this.enable) { console.log(`LoggerService: ${message}`); } } }
然后在組件構造函數中寫上需要的服務就好。
四、限定方式的依賴注入
想象一場景,你應用中的某個服務的provider被當做無效代碼刪掉了,那么你的應用可能就會出問題。還好這個問題早在設計的時候就已經考慮到了,我們可以使用Angular提供的@Optional和@Host裝飾器來解決這個問題。
Optional可以兼容依賴不存在的情況,提高系統的健壯性;@Host可以限定查找規則,明確實例化的位置,避免一些莫名的共享對象問題。
@Optional
借助@Optional就可以實現可選注入:
// app.component.ts // ... import { Optional } from '@angular/core'; constructor(@Optional() private logger: LoggerService) { if (this.logger) { this.logger.log('i am choosed'); } }
像例子中的那樣只需要在宿主組件(Host Component)的構造函數中增加@Optional裝飾器即可。
需要注意的是,上面例子中的LoggerService并不是不存在,只是并沒有根據providers元數據中配置被實例化出來。
@Host
Angular中依賴查找的規則是按照注入器從當前組件向父組件查找,直到找到要注入的依賴位置,如果找不到就會報錯。我們可以使用Angular提供的@Host裝飾器來解決 這個問題。
宿主組件如果一個組件注入了依賴項,那么這個組件就是這個依賴的宿主組件;如果這個組件通過<ng-content>被嵌入到了父組件,那這個父組件就是這個依賴的宿主組件。
1、宿主組件是當前組件
我們給組件構造函數加上@Host裝飾器:
// ... @Component({ selector: 'parent', template: ` <h2>這里是父組件</h2> ` }) constructor( @Host() logger: LoggerService) {} // 加上@Host之后會報錯,因為我們并沒有在這個組件中注入LoggerService // 但是我們可以加上@Optional來避免報錯 //@Host() //@Optional() //logger: LoggerService) {} )
2、宿主組件是父組件
我們修改一下上面的組件為父組件:
// parent.component.ts // ... @Component({ selector: 'parent', template: ` <h2>這里是父組件</h2> <ng-content></content> ` // 在父組件中注入 LoggerService providers: [LoggerService] }) constructor() {}
增加一個子組件:
// child.component.ts // ... @Component({ selector: 'child', template: ` <h2>這里是子組件</h2> ` }) constructor( @Host() @Optional() logger: LoggerService) ){}
當然<parent>標簽中應該這樣寫:
<parent> <child></child> </parent>
因為此時宿主組件是父組件,所以我們在父組件中注入LoggerService Angular注入器會自動向上查找,找到ParentComponet中的配置,從而完成注入。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Angular中依賴注入怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。