概念

Angular依赖注入系统中一个很重要的概念——服务提供者,有人使用服务,那么就应该有人提供服务

  • 提供者会告诉Angular怎样来找到对应的服务(通过DI令牌)、以及如何创建服务实例

  • 有四中类别的提供者:useClassuseExisinguseValueuseFactory

  • 组件类、模块类、服务类等的构造函数中只能是带有某种装饰器的类或者由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本质就是一个类,所以XXXValueXXXService的一个实例

// 根模块
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的一个实例

注意:

  • animalServicefruitService 是两个不同的AnimalService实例
  • animalServiceanimal是同一个AnimalService实例
  • fruitServicefruit是同一个AnimalService实例

所以修改根组件animalServicemessage属性值后不影响fruitService和home组件fruitmessage属性值,但会影响home组件中animalmessage属性值。

useExisting

providers: [
	BbService,
	{ provide: AaService, useExisting: BbService }
]

当构造函数需要AaService时,Angular DI从另一个keyBbServiceprovider里查找,取出对应的实例进行注入。

// 此时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
  }
}

注意:

  • animalServicefruitServiceanimalfruit指向同一个AnimalService实例

所以根组件修改animalServicemessage属性值后会影响到fruitServiceanimalfruitmessage属性值。

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);
  }
}

此时会报错
Angular 服务的使用(二)-小白菜博客
在概念中,我们记得有这么一句话:组件类、模块类、服务类等的构造函数中只能是带有某种装饰器的类或者由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会将这些令牌对应的服务注入到工厂函数的参数列表中