這篇文章主要介紹了Angular中如何使用依賴注入,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

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