概念
Angular
依赖注入系统中一个很重要的概念——服务提供者,有人使用服务,那么就应该有人提供服务
-
提供者会告诉
Angular
怎样来找到对应的服务(通过DI令牌)、以及如何创建服务实例 -
有四中类别的提供者:
useClass
、useExising
、useValue
、useFactory
-
组件类、模块类、服务类等的构造函数中只能是带有某种装饰器的类或者由
InjectionToken
创建的DI
令牌
DI
,全称为dependency injection
,译为依赖注入。Token
,即DI令牌
providers: [{provide: DI令牌, useClass/useExisting/useValue/useFactory: 服务类名}]
useClass
providers: [AnimalService]
等价于
providers: [{provide: AnimalService, useClass: AnimalService}]
当使用<提供者providers
>配置<注入器injector
>时(注入器就是那些被@injectable
修饰的类,可以理解为服务),Angular DI
系统会将该提供者与依赖项注入令牌(Token
或叫 DI 令牌
)从而将两者关联起来。注入器允许 Angular
创建任何内部依赖项的映射。 DI 令牌
会充当该映射的键名key
。依赖项值是一个实例,而这个类的类型用作查找键。
providers: [{provide: XXXClass, useClass: XXXValue}]
provide
: XXXService
就是DI令牌
,Angular DI
注册表里根据这个DI令牌
找到服务
useClass
: XXXValue
就是 new XXXService()
,XXXService
本质就是一个类,所以XXXValue
是XXXService
的一个实例
// 根模块
providers: [
{provide: AnimalService, useClass: AnimalService},
]
// A模块
providers: [
{provide: AaService, useClass: AaService},
]
// A模块下的X组件
providers: [
{provide: XxService, useClass: XxService},
]
// BC模块
providers: [
{provide: BbService, useClass: BbService},
{provide: CcService, useClass: CcService},
]
// Angular ID会将该提供者与依赖项注入令牌关联起来,并维护一个provider注册表,该注册表支持根据key查询provider
// provider注册表,可以简单理解为
const provider = {
AnimalService: new AnimalService(),
A: {
AaService: new AaService(),
X: {
XxService: new XxService(),
}
}
BC: {
BbService: new BbService(),
CcService: new CcService(),
}
...
}
写在构造函数中的那个你以为是服务类的类名其实是DI令牌
,下面的构造函数中使用 AaService
类的类型来定义构造函数参数,这个AaService
就是DI令牌
// A模块下的某组件
// 首先会在当前组件的providers中查找DI令牌为AaService的服务,若找不到便在当前组件的模块的providers中查找DI令牌为AaService的服务,直到根模块,如果找不到说明当前provider注册表没有这个DI令牌便会报错。
// 当前使用 AaService 类的类型来定义构造函数参数,Angular DI系统在A模块找到相关的DI令牌,此时会注入与这个该令牌相关联的服务
// 即Angular会在provider注册表查找key与类型相关联的依赖项值。此时构造函数参数aaService指向provider.A.Aaservice
constructor(private aaService: AaService) {}
示例:
定义动物服务类: AnimalService
// anmial.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class AnimalService {
message = 'I am AnimalService';
constructor() { }
}
定义水果服务类: FruitService
// fruit.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class FruitService {
message = 'I am FruitService';
constructor() { }
}
根模块提供者配置注入器
// app.module.ts
providers: [
AnimalService,
{ provide: FruitService, useClass: AnimalService },
]
在根模块组件使用
// app.component.ts
export class AppComponent {
message = 'I am AppComponent';
constructor(private animalService: AnimalService, private fruitService: FruitService) {
this.animalService.message = this.message;
console.log(this.fruitService.message); // I am AnimalService
}
}
在home组件使用
// home.component.ts
export class HomeComponent {
message = 'I am HomeComponent';
constructor(private animal: AnimalService, private fruit: FruitService) {
console.log(this.animal.message); // I am AppComponent
console.log(this.fruit.message); // I am AnimalService
}
}
提供者: 根模块的providers
注入器: 动物服务AnimalService
、 水果服务FruitService
配置根模块的providers
后,Angular
会将该提供者与依赖项注入DI令牌关联起来
AnimalService
是一个DI令牌,指向AnimalService
的一个实例FruitService
是一个DI令牌,指向AnimalService
的一个实例
根组件构造函数参数animalService
的类型是AnimalService
,根据ID令牌指向AnimalService
的一个实例
根组件构造函数参数fruitService
的类型是FruitService
,根据ID令牌指向AnimalService
的一个实例
home组件构造函数参数animal
的类型是AnimalService
,根据ID令牌指向AnimalService
的一个实例
home组件构造函数参数fruit
的类型是FruitService
,根据ID令牌指向AnimalService
的一个实例
注意:
animalService
和fruitService
是两个不同的AnimalService
实例animalService
和animal
是同一个AnimalService
实例fruitService
和fruit
是同一个AnimalService
实例
所以修改根组件animalService
的message
属性值后不影响fruitService
和home组件fruit
的message
属性值,但会影响home组件中animal
的message
属性值。
useExisting
providers: [
BbService,
{ provide: AaService, useExisting: BbService }
]
当构造函数需要AaService
时,Angular DI
从另一个key
为BbService
的provider
里查找,取出对应的实例进行注入。
// 此时aaService和bbService指向同一个BbService实例
constructor(private aaService: AaService, private bbService: BaService) {}
这里相当于AaService
令牌是BbService
令牌的一个别名,实际他们是同一个东西。
// 根模块
providers: [
BbService,
{ provide: AaService, useExisting: BbService }
]
// provider注册表,可以简单理解为
const BbService = new BbService()
const provide = {
BbService,
AaService: BbService
}
示例:
定义动物服务类: AnimalService
// anmial.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class AnimalService {
message = 'I am AnimalService';
constructor() { }
}
定义水果服务类: FruitService
// fruit.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class FruitService {
message = 'I am FruitService';
constructor() { }
}
根模块提供者配置注入器
// app.module.ts
providers: [
AnimalService,
{ provide: FruitService, useExisting: AnimalService },
]
在根模块组件使用
// app.component.ts
export class AppComponent {
message = 'I am AppComponent';
constructor(private animalService: AnimalService, private fruitService: FruitService) {
this.animalService.message = this.message;
console.log(this.fruitService.message); // I am AppComponent
}
}
在home组件使用
// home.component.ts
export class HomeComponent {
message = 'I am HomeComponent';
constructor(private animal: AnimalService, private fruit: FruitService) {
console.log(this.animal.message); // I am AppComponent
console.log(this.fruit.message); // I am AppComponent
}
}
注意:
animalService
、fruitService
、animal
、fruit
指向同一个AnimalService
实例
所以根组件修改animalService
的message
属性值后会影响到fruitService
、animal
和fruit
的message
属性值。
useValue
我们使用InjectionToken
来手动创建DI令牌
// DI.ts
import { InjectionToken } from '@angular/core';
// 通过InjectionToken的泛型参数我们可以知道,这个DI令牌使用来表示string类型的服务
export const MY_DI = new InjectionToken<string>('自定义DI令牌的描述');
我们创建了MY_DI
这个DI令牌
,使用useValue
providers: [
{ provide: MY_DI, useValue: 'I am a DI令牌'},
]
在构造函数如何使用呢这个MY_DI
这个DI令牌
呢?
我们可以使用@Inject()这个修饰器,里面的参数就是我们新建的DI令牌。这里输出的内容就是我们在配置提供者时useValue
的值。
constructor(@Inject(MY_DI) private myDI: string) {
console.log(myDI) // I am a DI令牌
}
useFactory
现在有这样一个场景:动物服务类构造函数依赖水果服务类,并且有个isMammal参数判断是否是哺乳动物
// anmial.service.ts
import { Injectable } from '@angular/core';
import { FruitService } from './fruit.service'
@Injectable({
providedIn: 'root'
})
export class AnimalService {
message = 'I am AnimalService';
constructor(private fruitService: FruitService, private isMammal: boolean) {
console.log('是否是哺乳动物?', isMammal)
}
}
在根组件使用
// app.component.ts
export class AppComponent {
message = 'I am AppComponent';
constructor(private animalService: AnimalService) {
this.animalService.message = this.message;
console.log(this.animalService.message);
}
}
此时会报错
在概念中,我们记得有这么一句话:组件类、模块类、服务类等的构造函数中只能是带有某种装饰器的类或者由InjectionToken
创建的DI
令牌
AnimalService
构造函数的两个参数
第一个FruitService是带有@Injectable
装饰器的类
第二个参数既不是带有Angular
某种装饰器,也没有使用InjectionToken
创建的DI令牌
所以问题就出在第二个参数上
此时我们这里就要用useFactory
来解决
第一步:创建factoryFun.ts
文件
// factoryFun.ts
import { AnimalService } from './animal.service';
import { FruitService } from './fruit.service';
export const factoryFun = (fruitService: FruitService) => {
return new AnimalService(fruitService, true);
};
第二步:修改app.module.ts
文件中providers
配置项
// app.module.ts
providers: [
{
provide: AnimalService,
useFactory: factoryFun,
deps: [FruitService]
}
]
1.该服务提供者的令牌还是AnimalService
2.useFactory
的值是factoryFun
,是一个工厂函数
3.deps
数组:DI令牌
数组,Angular
会将这些令牌对应的服务注入到工厂函数的参数列表中