-->
Angular 官方文档对于依赖提供者,也就是providers
的解释如下:
通过配置提供者,你可以把服务提供给那些需要它们的应用部件。
依赖提供者会使用 DI 令牌来配置注入器,注入器会用它来提供这个依赖值的具体的、运行时版本
简单总结依赖提供者
做了两件事:
在之前的示例中,当使用@Inject(HeroService)
注入一个服务时,Angular 注入器会通过new HeroService()
实例化一个类返回依赖值,类实例化其实就是一种如何提供依赖值的方式,那么 Angular 中除了类实例化提供依赖值外还提供给了如下类型的Provider
,每种Provider
都有其使用场景。
export declare type Provider = TypeProvider | ValueProvider | ClassProvider
| ConstructorProvider | ExistingProvider | FactoryProvider | any[];
export declare type StaticProvider = ValueProvider | ExistingProvider |
StaticClassProvider | ConstructorProvider | FactoryProvider | any[];
关于限制服务可使用的范围就更好理解了,满足只能在某个模块或者组件注入HeroService
的场景。
在组件或者模块中通过装饰器元数据providers
定义提供者。
比如:类提供者
useClass
—— 就像这个例子中一样。 也可以是 useExisting
、useValue
或useFactory
, 每一个 key 都用于提供一种不同类型的依赖。类型: TypeProvider
和ClassProvider
,类提供者应该是最常用的一种,在依赖注入简介章节的示例就是,简写和全写的配置如下:
{
provides: [ Logger ] // 简写
provides: [{ provide: Logger, useClass: Logger }] // 全写
}
provide: Logger
意思是把类的类型作为 DI Token(依赖令牌)。useClass
表示使用此类实例化作为依赖值,其实就是通过new Logger()
返回依赖值。使用场景:
Logger
,但是实际返回的对象是BetterLogger
示例。[{ provide: Logger, useClass: BetterLogger }]
// 当使用 Logger 令牌请求 Logger 时,返回一个 BetterLogger
类型: ExistingProvider
,在下面的例子中,当组件请求新的或旧的 Logger 时,注入器都会注入一个 NewLogger
的实例。 通过这种方式, OldLogger
就成了NewLogger
的别名。
[
NewLogger,
// Alias OldLogger reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}
]
那么别名类提供者和类提供者有什么区别呢?
[
NewLogger,
{ provide: OldLogger, useClass: NewLogger}
]
useExisting
值是一个 DI Token ,provide 也是一个 DI Token, 2个 Token 指向同一个实例。useClass
值是一个可以实例化的类,也就是可以 new 出来的类,这个类可以是任何类。使用场景:
Logger
类的返回的方法和属性太多,当前场景只需要使用少量的属性和函数,可以定义一个简化版的 MinimalLogger,通过注入MinimalLogger
使用,运行时返回的其实还是Logger
对象。// Class used as a "narrowing" interface that exposes a minimal logger
// Other members of the actual implementation are invisible
export abstract class MinimalLogger {
abstract logs: string[];
abstract logInfo: (msg: string) => void;
}
{ provide: MinimalLogger, useExisting: LoggerService },
// parent.ts
class abstract Parent {
...
}
// alex.component.ts
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }]
class AlexComponent {
// ChildComponent
}
// child.component.ts
class ChildComponent {
constructor(parent: Parent)
}
类型: ValueProvider
,要注入一个对象,可以用useValue
选项来配置注入器,下面的提供者定义对象使用useValue
作为 key 来把该变量与Logger
令牌关联起来。
// An object in the shape of the logger service
function silentLoggerFn() {}
export const silentLogger = {
logs: ['Silent logger says "hahaha!". Provided via "useValue"'],
log: silentLoggerFn
};
[{ provide: Logger, useValue: silentLogger }]
使用场景:
通过对象提供者注入一个配置对象,一般推荐使用InjectionToken
作为令牌。
// src/app/app.config.ts
import { InjectionToken } from '@angular/core';
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};
// src/app/app.module.ts (providers)
providers: [
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],
当使用InjectionToken
作为令牌时,在组件或者服务中必须借助参数装饰器@Inject()
,才可以把这个配置对象注入到构造函数中。
// src/app/app.component.ts
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
同时在定义InjectionToken
的时候还可以设置providedIn
和factory
。
export const TOKEN_FACTORY = new InjectionToken('factory-token', {
providedIn: 'root',
factory: () => {
return 'I am from InjectionToken factory';
}
});
类型: FactoryProvider
,要想根据运行前尚不可用的信息创建可变的依赖值,可以使用工厂提供者。也就是完全自己决定如何创建依赖。
比如:只有授权用户才能看到HeroService
中的秘密英雄。
// src/app/heroes/hero.service.ts (excerpt)
constructor(
private logger: Logger,
private isAuthorized: boolean) { }
getHeroes() {
const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}
// src/app/heroes/hero.service.provider.ts (excerpt)
const heroServiceFactory = (logger: Logger, userService: UserService) => {
return new HeroService(logger, userService.user.isAuthorized);
};
export const heroServiceProvider =
{
provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService]
};
useFactory
字段指定该提供者是一个工厂函数,其实现代码是heroServiceFactory
。deps
属性是一个提供者令牌数组,Logger
和UserService
类都是类提供者的令牌。该注入器解析了这些令牌,并把相应的服务注入到heroServiceFactory
工厂函数的参数中。理解了工厂提供给者后再回过头看,useValue
、useClass
和useExisting
的区别就更简单了,provider
就是在装饰器中通过providers
数组配置的元数据对象。
虽然 TypeScript 的AppConfig
接口可以在类中提供类型支持,但它在依赖注入时却没有任何作用。在 TypeScript 中,接口只能作为类型检查,它没有可供 DI 框架使用的运行时表示形式或令牌。
// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
// Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }
当配置提供者时设置multi
为 true 时,通过@Inject(DIToken)
参数注入获取的依赖值就会返回一个数组。
export declare interface ClassProvider extends ClassSansProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* When true, injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean;
}
使用场景:
内置 API ,比如:NG_VALUE_ACCESSOR
、HTTP_INTERCEPTORS
、APP_INITIALIZER
等
@Component({
selector: 'select',
templateUrl: './select.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ThySelectComponent),
multi: true
}
]
})
export class ThySelectComponent implements ControlValueAccessor {}
以上就是依赖提供者 provider 相关的介绍,理解了 factory 提供依赖值后再看其他类型就会简单很多,其他的类型就是 factory 之上高级的 API 而已,满足不同的场景需要,这是 Angular 依赖注入入门比较难懂的知识,那么接下来的多级注入器是另一个重要的知识点,这两部分都深入理解那么 Angular 依赖注入就不在是难点了。