在 Angular 開(kāi)發(fā)中,循環(huán)依賴是一個(gè)常見(jiàn)且棘手的問(wèn)題,而?InjectionToken?是解決循環(huán)依賴的有效手段之一。下面詳細(xì)介紹 Angular 中的循環(huán)依賴問(wèn)題以及如何使用?InjectionToken?來(lái)解決它。
1. 什么是循環(huán)依賴
循環(huán)依賴指的是兩個(gè)或多個(gè)服務(wù)之間相互依賴,形成一個(gè)閉環(huán)。例如,服務(wù) A 依賴于服務(wù) B,而服務(wù) B 又依賴于服務(wù) A,這樣在創(chuàng)建這些服務(wù)的實(shí)例時(shí),就會(huì)陷入無(wú)限循環(huán),導(dǎo)致程序無(wú)法正常運(yùn)行。
以下是一個(gè)簡(jiǎn)單的循環(huán)依賴示例:
import { Injectable } from '@angular/core';
@Injectable({
? providedIn: 'root'
})
export class ServiceA {
? constructor(private serviceB: ServiceB) {}
}
@Injectable({
? providedIn: 'root'
})
export class ServiceB {
? constructor(private serviceA: ServiceA) {}
}
在上述代碼中,ServiceA?的構(gòu)造函數(shù)依賴于?ServiceB,而?ServiceB?的構(gòu)造函數(shù)又依賴于?ServiceA,這就形成了循環(huán)依賴。當(dāng) Angular 嘗試創(chuàng)建?ServiceA?的實(shí)例時(shí),需要先創(chuàng)建?ServiceB?的實(shí)例,而創(chuàng)建?ServiceB?的實(shí)例又需要先創(chuàng)建?ServiceA?的實(shí)例,從而陷入無(wú)限循環(huán)。
2.?InjectionToken?介紹
InjectionToken?是 Angular 提供的一種機(jī)制,用于創(chuàng)建唯一的令牌,它可以作為依賴注入的標(biāo)識(shí)符。與直接使用類名作為依賴注入的令牌不同,InjectionToken?可以避免命名沖突,并且可以用于注入非類的依賴項(xiàng),如配置對(duì)象、常量等。
InjectionToken?的創(chuàng)建方式如下:
import { InjectionToken } from '@angular/core';
export const MY_TOKEN = new InjectionToken<string>('MY_TOKEN');
這里創(chuàng)建了一個(gè)名為?MY_TOKEN?的?InjectionToken,它的泛型參數(shù)指定了該令牌所代表的依賴項(xiàng)的類型,這里是?string?類型。
3. 使用?InjectionToken?解決循環(huán)依賴
下面通過(guò)一個(gè)示例來(lái)展示如何使用?InjectionToken?解決循環(huán)依賴問(wèn)題。假設(shè)我們有兩個(gè)服務(wù)?ServiceA?和?ServiceB,它們之間存在循環(huán)依賴,我們可以使用?InjectionToken?來(lái)打破這個(gè)循環(huán)。
import { Injectable, Inject, InjectionToken } from '@angular/core';
// 創(chuàng)建一個(gè) InjectionToken 用于標(biāo)識(shí)原始的 ServiceB
export const ORIGINAL_SERVICE_B = new InjectionToken<ServiceB>('ORIGINAL_SERVICE_B');
@Injectable({
? providedIn: 'root'
})
export class ServiceA {
? constructor(@Inject(ORIGINAL_SERVICE_B) private serviceB: ServiceB) {}
}
@Injectable({
? providedIn: 'root'
})
export class ServiceB {
? constructor(private serviceA: ServiceA) {}
}
然后在模塊的?providers?數(shù)組中進(jìn)行配置:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ServiceA, ServiceB, ORIGINAL_SERVICE_B } from './your-services-file';
@NgModule({
? declarations: [AppComponent],
? imports: [BrowserModule],
? providers: [
? ? { provide: ORIGINAL_SERVICE_B, useClass: ServiceB },
? ? { provide: ServiceB, useClass: ServiceB } // 這里可以根據(jù)需要替換為擴(kuò)展后的 ServiceB
? ],
? bootstrap: [AppComponent]
})
export class AppModule {}
代碼解釋
創(chuàng)建?InjectionToken:ORIGINAL_SERVICE_B?是一個(gè)?InjectionToken,用于標(biāo)識(shí)原始的?ServiceB。
修改?ServiceA?的構(gòu)造函數(shù):在?ServiceA?的構(gòu)造函數(shù)中,使用?@Inject(ORIGINAL_SERVICE_B)?注入?ServiceB?的實(shí)例,這樣就避免了直接使用?ServiceB?類名,從而打破了循環(huán)依賴。
模塊配置:在?AppModule?的?providers?數(shù)組中,將?ORIGINAL_SERVICE_B?與?ServiceB?類關(guān)聯(lián)起來(lái),當(dāng)請(qǐng)求?ORIGINAL_SERVICE_B?時(shí),Angular 會(huì)創(chuàng)建一個(gè)?ServiceB?的實(shí)例。同時(shí),也可以根據(jù)需要將?ServiceB?替換為擴(kuò)展后的服務(wù)類。
通過(guò)這種方式,我們使用?InjectionToken?成功解決了?ServiceA?和?ServiceB?之間的循環(huán)依賴問(wèn)題。
其他解決循環(huán)依賴的方法
除了使用?InjectionToken,還可以通過(guò)以下方法解決循環(huán)依賴問(wèn)題:
重構(gòu)代碼:重新設(shè)計(jì)服務(wù)的結(jié)構(gòu),避免出現(xiàn)循環(huán)依賴。例如,將公共的邏輯提取到一個(gè)新的服務(wù)中,讓?ServiceA?和?ServiceB?都依賴于這個(gè)新服務(wù)。
使用 setter 注入:在構(gòu)造函數(shù)中不直接注入依賴項(xiàng),而是通過(guò) setter 方法在實(shí)例創(chuàng)建后再注入依賴項(xiàng)。這樣可以避免在創(chuàng)建實(shí)例時(shí)就陷入循環(huán)依賴。
import { Injectable } from '@angular/core';
@Injectable({
? providedIn: 'root'
})
export class ServiceA {
? private _serviceB: ServiceB;
? set serviceB(serviceB: ServiceB) {
? ? this._serviceB = serviceB;
? }
}
@Injectable({
? providedIn: 'root'
})
export class ServiceB {
? constructor(private serviceA: ServiceA) {
? ? this.serviceA.serviceB = this;
? }
}
這種方法通過(guò)在實(shí)例創(chuàng)建后再注入依賴項(xiàng),避免了構(gòu)造函數(shù)中的循環(huán)依賴問(wèn)題。但需要注意的是,這種方法可能會(huì)使代碼的依賴關(guān)系不夠清晰,需要謹(jǐn)慎使用。