這篇文章主要介紹了Angular中如何使用依賴注入,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
成都創(chuàng)新互聯(lián)公司是一家專注于網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)與策劃設(shè)計(jì),鶴山網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:鶴山等地區(qū)。鶴山做網(wǎng)站價(jià)格咨詢:18980820575
provider
的應(yīng)用場(chǎng)景下面,我們通過(guò)實(shí)際例子,來(lái)對(duì)幾個(gè)提供商的使用場(chǎng)景進(jìn)行說(shuō)明。
某天,咱們接到一個(gè)需求:實(shí)現(xiàn)一個(gè)本地存儲(chǔ)
的功能,并將其注入
到Angular
應(yīng)用中,使其可以在系統(tǒng)中全局使用
首先編寫服務(wù)類storage.service.ts
,實(shí)現(xiàn)其存儲(chǔ)功能
// storage.service.ts export class StorageService { get(key: string) { return JSON.parse(localStorage.getItem(key) || '{}') || {}; } set(key: string, value: ITokenModel | null): boolean { localStorage.setItem(key, JSON.stringify(value)); return true; } remove(key: string) { localStorage.removeItem(key); } }
如果你馬上在user.component.ts
中嘗試使用
// user.component.ts @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class CourseCardComponent { constructor(private storageService: StorageService) { ... } ... }
你應(yīng)該會(huì)看到這樣的一個(gè)錯(cuò)誤:
NullInjectorError: No provider for StorageService!
顯而易見(jiàn),我們并沒(méi)有將StorageService
添加到 Angular的依賴注入系統(tǒng)
中。Angular
無(wú)法獲取StorageService
依賴項(xiàng)的Provider
,也就無(wú)法實(shí)例化這個(gè)類,更沒(méi)法調(diào)用類中的方法。
接下來(lái),我們本著缺撒補(bǔ)撒的理念,手動(dòng)添加一個(gè)Provider
。修改storage.service.ts
文件如下
// storage.service.ts export class StorageService { get(key: string) { return JSON.parse(localStorage.getItem(key) || '{}') || {}; } set(key: string, value: any) { localStorage.setItem(key, JSON.stringify(value)); } remove(key: string) { localStorage.removeItem(key); } } // 添加工廠函數(shù),實(shí)例化StorageService export storageServiceProviderFactory(): StorageService { return new StorageService(); }
通過(guò)上述代碼,我們已經(jīng)有了Provider
。那么接下來(lái)的問(wèn)題,就是如果讓Angular
每次掃描到StorageService
這個(gè)依賴項(xiàng)的時(shí)候,讓其去執(zhí)行storageServiceProviderFactory
方法,來(lái)創(chuàng)建實(shí)例
這就引出來(lái)了下一個(gè)知識(shí)點(diǎn)InjectionToken
在一個(gè)服務(wù)類中,我們常常需要添加多個(gè)依賴項(xiàng),來(lái)保證服務(wù)的可用。而InjectionToken
是各個(gè)依賴項(xiàng)的唯一標(biāo)識(shí),它讓Angular
的依賴注入系統(tǒng)能準(zhǔn)確的找到各個(gè)依賴項(xiàng)的Provider
。
接下來(lái),我們手動(dòng)添加一個(gè)InjectionToken
// storage.service.ts import { InjectionToken } from '@angular/core'; export class StorageService { get(key: string) { return JSON.parse(localStorage.getItem(key) || '{}') || {}; } set(key: string, value: any) { localStorage.setItem(key, JSON.stringify(value)); } remove(key: string) { localStorage.removeItem(key); } } export storageServiceProviderFactory(): StorageService { return new StorageService(); } // 添加StorageServiced的InjectionToken export const STORAGE_SERVICE_TOKEN = new InjectionToken('AUTH_STORE_TOKEN');
ok,我們已經(jīng)有了StorageService
的Provider
和InjectionToken
。
接下來(lái),我們需要一個(gè)配置,讓Angular
的依賴注入系統(tǒng)
能夠?qū)ζ溥M(jìn)行識(shí)別,在掃描到StorageService
(Dependency)的時(shí)候,根據(jù)STORAGE_SERVICE_TOKEN
(InjectionToken)去找到對(duì)應(yīng)的storageServiceProviderFactory
(Provider),然后創(chuàng)建這個(gè)依賴項(xiàng)的實(shí)例。如下,我們?cè)?code>module中的@NgModule()
裝飾器中進(jìn)行配置:
// user.module.ts @NgModule({ imports: [ ... ], declarations: [ ... ], providers: [ { provide: STORAGE_SERVICE_TOKEN, // 與依賴項(xiàng)關(guān)聯(lián)的InjectionToken,用于控制工廠函數(shù)的調(diào)用 useFactory: storageServiceProviderFactory, // 當(dāng)需要?jiǎng)?chuàng)建并注入依賴項(xiàng)時(shí),調(diào)用該工廠函數(shù) deps: [] // 如果StorageService還有其他依賴項(xiàng),這里添加 } ] }) export class UserModule { }
到這里,我們完成了依賴
的實(shí)現(xiàn)。最后,還需要讓Angular
知道在哪里注入
。Angular
提供了 @Inject
裝飾器來(lái)識(shí)別
// user.component.ts @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class CourseCardComponent { constructor(@Inject(STORAGE_SERVICE_TOKEN) private storageService: StorageService) { ... } ... }
到此,我們便可以在user.component.ts
調(diào)用StorageService
里面的方法了
emm...你是否覺(jué)得上述的寫法過(guò)于復(fù)雜了,而在實(shí)際開發(fā)中,我們大多數(shù)場(chǎng)景是無(wú)需手動(dòng)創(chuàng)建Provider
和InjectionToken
的。如下:
// user.component.ts @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class CourseCardComponent { constructor(private storageService: StorageService) { ... } ... } // storage.service.ts @Injectable({ providedIn: 'root' }) export class StorageService {} // user.module.ts @NgModule({ imports: [ ... ], declarations: [ ... ], providers: [StorageService] }) export class UserModule { }
下面,我們來(lái)對(duì)上述的這種簡(jiǎn)寫模式
進(jìn)行剖析。
在user.component.ts
,我們舍棄了@Inject
裝飾器,直接添加依賴項(xiàng)private storageService: StorageService
,這得益于Angular
對(duì)InjectionToken
的設(shè)計(jì)。
InjectionToken
不一定必須是一個(gè)InjectionToken object
,只要保證它在運(yùn)行時(shí)環(huán)境中能夠識(shí)別對(duì)應(yīng)的唯一依賴項(xiàng)
即可。所以,在這里,你可以用類名
即運(yùn)行時(shí)中的構(gòu)造函數(shù)名稱
來(lái)作為依賴項(xiàng)
的InjectionToken
。省略創(chuàng)建InjectionToken
這一步驟。
// user.module.ts @NgModule({ imports: [ ... ], declarations: [ ... ], providers: [{ provide: StorageService, // 使用構(gòu)造函數(shù)名作為InjectionToken useFactory: storageServiceProviderFactory, deps: [] }] }) export class UserModule { }
注意:由于Angular
的依賴注入系統(tǒng)
是在運(yùn)行時(shí)環(huán)境
中根據(jù)InjectionToken
識(shí)別依賴項(xiàng),進(jìn)行依賴注入的。所以這里不能使用interface
名稱作為InjectionToken
,因?yàn)槠渲淮嬖谟?code>Typescript語(yǔ)言的編譯期,并不存在于運(yùn)行時(shí)中。而對(duì)于類名
來(lái)說(shuō),其在運(yùn)行時(shí)環(huán)境中以構(gòu)造函數(shù)名
體現(xiàn),可以使用。
接下來(lái),我們可以使用useClass
替換useFactory
,其實(shí)也能達(dá)到創(chuàng)建實(shí)例的效果,如下:
... providers: [{ provide: StorageService, useClass: StorageService, deps: [] }] ...
當(dāng)使用useClass
時(shí),Angular
會(huì)將后面的值當(dāng)作一個(gè)構(gòu)造函數(shù)
,在運(yùn)行時(shí)環(huán)境中,直接執(zhí)行new
指令進(jìn)行實(shí)例化,這也無(wú)需我們?cè)偈謩?dòng)創(chuàng)建 Provider
了
當(dāng)然,基于Angular
的依賴注入設(shè)計(jì)
,我們可以寫得更簡(jiǎn)單
... providers: [StorageService] ...
直接寫入類名
到providers
數(shù)組中,Angular
會(huì)識(shí)別其是一個(gè)構(gòu)造函數(shù)
,然后檢查函數(shù)內(nèi)部,創(chuàng)建一個(gè)工廠函數(shù)去查找其構(gòu)造函數(shù)
中的依賴項(xiàng)
,最后再實(shí)例化
useClass
還有一個(gè)特性是,Angular
會(huì)根據(jù)依賴項(xiàng)
在Typescript
中的類型定義,作為其運(yùn)行時(shí)
的InjectionToken
去自動(dòng)查找Provider
。所以,我們也無(wú)需使用@Inject
裝飾器來(lái)告訴Angular
在哪里注入了
你可以簡(jiǎn)寫如下
... // 無(wú)需手動(dòng)注入 :constructor(@Inject(StorageService) private storageService: StorageService) constructor(private storageService: StorageService) { ... } ...
這也就是我們平常開發(fā)中,最常見(jiàn)的一種寫法。
完成本地存儲(chǔ)服務(wù)
的實(shí)現(xiàn)后,我們又收到了一個(gè)新需求,研發(fā)老大希望提供一個(gè)配置文件,來(lái)存儲(chǔ)StorageService
的一些默認(rèn)行為
我們先創(chuàng)建一個(gè)配置
const storageConfig = { suffix: 'app_' // 添加一個(gè)存儲(chǔ)key的前綴 expires: 24 * 3600 * 100 // 過(guò)期時(shí)間,毫秒戳 }
而useValue
則可以 cover 住這種場(chǎng)景。其可以是一個(gè)普通變量,也可以是一個(gè)對(duì)象形式。
配置方法如下:
// config.ts export interface STORAGE_CONFIG = { suffix: string; expires: number; } export const STORAGE_CONFIG_TOKEN = new InjectionToken('storage-config'); export const storageConfig = { suffix: 'app_' // 添加一個(gè)存儲(chǔ)key的前綴 expires: 24 * 3600 * 100 // 過(guò)期時(shí)間,毫秒戳 } // user.module.ts @NgModule({ ... providers: [ StorageService, { provide: STORAGE_CONFIG_TOKEN, useValue: storageConfig } ], ... }) export class UserModule {}
在user.component.ts
組件中,直接使用配置對(duì)象
:
// user.component.ts @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class CourseCardComponent { constructor(private storageService: StorageService, @Inject(STORAGE_CONFIG_TOKEN) private storageConfig: StorageConfig) { ... } getKey(): void { const { suffix } = this.storageConfig; console.log(this.storageService.get(suffix + 'demo')); } }
如果我們需要基于一個(gè)已存在的provider
來(lái)創(chuàng)建一個(gè)新的provider
,或需要重命名一個(gè)已存在的provider
時(shí),可以用useExisting
屬性來(lái)處理。比如:創(chuàng)建一個(gè)angular
的表單控件,其在一個(gè)表單中會(huì)存在多個(gè),每個(gè)表單控件存儲(chǔ)不同的值。我們可以基于已有的表單控件provider
來(lái)創(chuàng)建
// new-input.component.ts import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'new-input', exportAs: 'newInput', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NewInputComponent), // 這里的NewInputComponent已經(jīng)聲明了,但還沒(méi)有被定義。無(wú)法直接使用,使用forwardRef可以創(chuàng)建一個(gè)間接引用,Angular在后續(xù)在解析該引用 multi: true } ] }) export class NewInputComponent implements ControlValueAccessor { ... }
在Angular
中有兩個(gè)注入器層次結(jié)構(gòu)
ModuleInjector —— 使用 @NgModule() 或 @Injectable() 的方式在模塊中注入
ElementInjector —— 在 @Directive() 或 @Component() 的 providers 屬性中進(jìn)行配置
我們通過(guò)一個(gè)實(shí)際例子來(lái)解釋兩種注入器的應(yīng)用場(chǎng)景,比如:設(shè)計(jì)一個(gè)展示用戶信息的卡片組件
我們使用user-card.component.ts
來(lái)顯示組件,用UserService
來(lái)存取該用戶的信息
// user-card.component.ts @Component({ selector: 'user-card.component.ts', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.less'] }) export class UserCardComponent { ... } // user.service.ts @Injectable({ providedIn: "root" }) export class UserService { ... }
上述代碼是通過(guò)@Injectable
添加到根模塊
中,root
即根模塊的別名。其等價(jià)于下面的代碼
// user.service.ts export class UserService { ... } // app.module.ts @NgModule({ ... providers: [UserService], // 通過(guò)providers添加 }) export class AppModule {}
當(dāng)然,如果你覺(jué)得UserService
只會(huì)在UserModule
模塊下使用的話,你大可不必將其添加到根模塊
中,添加到所在模塊即可
// user.service.ts @Injectable({ providedIn: UserModule }) export class UserService { ... }
如果你足夠細(xì)心,會(huì)發(fā)現(xiàn)上述例子中,我們既可以通過(guò)在當(dāng)前service
文件中的@Injectable({ provideIn: xxx })
定義provider
,也可以在其所屬module
中的@NgModule({ providers: [xxx] })
定義。那么,他們有什么區(qū)別呢?
@Injectable()
和@NgModule()
除了使用方式不同外,還有一個(gè)很大的區(qū)別是:
使用 @Injectable() 的 providedIn 屬性優(yōu)于 @NgModule() 的 providers 數(shù)組,因?yàn)槭褂?@Injectable() 的 providedIn 時(shí),優(yōu)化工具可以進(jìn)行
搖樹優(yōu)化 Tree Shaking
,從而刪除你的應(yīng)用程序中未使用的服務(wù),以減小捆綁包尺寸。
我們通過(guò)一個(gè)例子來(lái)解釋上面的概述。隨著業(yè)務(wù)的增長(zhǎng),我們擴(kuò)展了UserService1
和UserService2
兩個(gè)服務(wù),但由于某些原因,UserService2
一直未被使用。
如果通過(guò)@NgModule()
的providers
引入依賴項(xiàng),我們需要在user.module.ts
文件中引入對(duì)應(yīng)的user1.service.ts
和user2.service.ts
文件,然后在providers
數(shù)組中添加UserService1
和UserService2
引用。而由于UserService2
所在文件在module
文件中被引用,導(dǎo)致Angular
中的tree shaker
錯(cuò)誤的認(rèn)為這個(gè)UserService2
已經(jīng)被使用了。無(wú)法進(jìn)行搖樹優(yōu)化
。代碼示例如下:
// user.module.ts import UserService1 from './user1.service.ts'; import UserService2 from './user2.service.ts'; @NgModule({ ... providers: [UserService1, UserService2], // 通過(guò)providers添加 }) export class UserModule {}
那么,如果通過(guò)@Injectable({providedIn: UserModule})
這種方式,我們實(shí)際是在服務(wù)類
自身文件中引用了use.module.ts
,并為其定義了一個(gè)provider
。無(wú)需在UserModule
中在重復(fù)定義,也就不需要在引入user2.service.ts
文件了。所以,當(dāng)UserService2
沒(méi)有被依賴時(shí),即可被優(yōu)化掉。代碼示例如下:
// user2.service.ts import UserModule from './user.module.ts'; @Injectable({ providedIn: UserModule }) export class UserService2 { ... }
在了解完ModuleInjector
后,我們繼續(xù)通過(guò)剛才的例子講述ElementInjector
。
最初,我們系統(tǒng)中的用戶只有一個(gè),我們也只需要一個(gè)組件和一個(gè)UserService
來(lái)存取這個(gè)用戶的信息即可
// user-card.component.ts @Component({ selector: 'user-card.component.ts', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.less'] }) export class UserCardComponent { ... } // user.service.ts @Injectable({ providedIn: "root" }) export class UserService { ... }
注意:上述代碼將UserService
被添加到根模塊
中,它僅會(huì)被實(shí)例化一次。
如果這時(shí)候系統(tǒng)中有多個(gè)用戶,每個(gè)用戶卡片組件
里的UserService
需存取對(duì)應(yīng)用戶的信息。如果還是按照上述的方法,UserService
只會(huì)生成一個(gè)實(shí)例。那么就可能出現(xiàn),張三存了數(shù)據(jù)后,李四去取數(shù)據(jù),取到的是張三的結(jié)果。
那么,我們有辦法實(shí)例化多個(gè)UserService
,讓每個(gè)用戶的數(shù)據(jù)存取操作隔離開么?
答案是有的。我們需要在user.component.ts
文件中使用ElementInjector
,將UserService
的provider
添加即可。如下:
// user-card.component.ts @Component({ selector: 'user-card.component.ts', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.less'], providers: [UserService] }) export class UserCardComponent { ... }
通過(guò)上述代碼,每個(gè)用戶卡片組件
都會(huì)實(shí)例化一個(gè)UserService
,來(lái)存取各自的用戶信息。
如果要解釋上述的現(xiàn)象,就需要說(shuō)到Angular
的Components and Module Hierarchical Dependency Injection
。
在組件中使用依賴項(xiàng)時(shí),
Angular
會(huì)優(yōu)先在該組件的providers
中尋找,判斷該依賴項(xiàng)是否有匹配的provider
。如果有,則直接實(shí)例化。如果沒(méi)有,則查找父組件的providers
,如果還是沒(méi)有,則繼續(xù)找父級(jí)的父級(jí),直到根組件
(app.component.ts)。如果在根組件
中找到了匹配的provider
,會(huì)先判斷其是否有存在的實(shí)例,如果有,則直接返回該實(shí)例。如果沒(méi)有,則執(zhí)行實(shí)例化操作。如果根組件
仍未找到,則開始從原組件
所在的module
開始查找,如果原組件
所在module
不存在,則繼續(xù)查找父級(jí)module
,直到根模塊
(app.module.ts)。最后,仍未找到則報(bào)錯(cuò)No provider for xxx
。
在Angular
應(yīng)用中,當(dāng)依賴項(xiàng)
尋找provider
時(shí),我們可以通過(guò)一些修飾符來(lái)對(duì)搜索結(jié)果進(jìn)行容錯(cuò)處理或限制搜索的范圍。
通過(guò)
@Optional()
裝飾服務(wù),表明讓該服務(wù)可選。即如果在程序中,沒(méi)有找到服務(wù)匹配的provider
,也不會(huì)程序崩潰,報(bào)錯(cuò)No provider for xxx
,而是返回null
。
export class UserCardComponent { constructor(@Optional private userService: UserService) {} }
使用
@Self()
讓Angular
僅查看當(dāng)前組件或指令的ElementInjector
。
如下,Angular
只會(huì)在當(dāng)前UserCardComponent
的providers
中搜索匹配的provider
,如果未匹配,則直接報(bào)錯(cuò)。No provider for UserService
。
// user-card.component.ts @Component({ selector: 'user-card.component.ts', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.less'], providers: [UserService], }) export class UserCardComponent { constructor(@Self() private userService?: UserService) {} }
@SkipSelf()
與@Self()
相反。使用@SkipSelf()
,Angular
在父ElementInjector
中而不是當(dāng)前ElementInjector
中開始搜索服務(wù).
// 子組件 user-card.component.ts @Component({ selector: 'user-card.component.ts', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.less'], providers: [UserService], // not work }) export class UserCardComponent { constructor(@SkipSelf() private userService?: UserService) {} } // 父組件 parent-card.component.ts @Component({ selector: 'parent-card.component.ts', templateUrl: './parent-card.component.html', styleUrls: ['./parent-card.component.less'], providers: [ { provide: UserService, useClass: ParentUserService, // work }, ], }) export class ParentCardComponent { constructor() {} }
@Host()
使你可以在搜索provider
時(shí)將當(dāng)前組件指定為注入器樹的最后一站。這和@Self()
類似,即使樹的更上級(jí)有一個(gè)服務(wù)實(shí)例,Angular
也不會(huì)繼續(xù)尋找。
某些場(chǎng)景下,我們需要一個(gè)InjectionToken
初始化多個(gè)provider
。比如:在使用攔截器的時(shí)候,我們希望在default.interceptor.ts
之前添加一個(gè) 用于 token 校驗(yàn)的JWTInterceptor
... const NET_PROVIDES = [ { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true } ]; ...
multi: 為false
時(shí),provider
的值會(huì)被覆蓋;設(shè)置為true
,將生成多個(gè)provider
并與唯一InjectionToken
HTTP_INTERCEPTORS
關(guān)聯(lián)。最后可以通過(guò)HTTP_INTERCEPTORS
獲取所有provider
的值
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Angular中如何使用依賴注入”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!