这里主要讲一下Provider注册方式,需要使用@Inject
正常我们注册一个服务像下面一样,假设一个logService服务
providers: [logService],
实际上它的完整形式如下
providers: [{provide: logService, useClass: logService}]
其中provide属性是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。useClass则告诉provider用那个服务类去创建实例。
Provider注册方式
类Provider ( ClassProvider )
值Provider ( ValueProvider )
别名Provider ( ExistingProvider )
工厂Provider ( FactoryProvider 主要用于动态创建依赖)
类Provider
类Provider就像上面的例子一样,用useClass服务类来创建实例,用provide声明标识。
值Provider
值Provider指定依赖对象为常量、字符串、对象或其他数据类型的值。在创建Provider对象是使用useValue。
providers: [{provide: 'name', useValue: '王小二'}],
在使用时需要在构造函数中使用@Inject(),例如
constructor(@Inject('name') public n: string) {}
别名Provider
让多个标识同时指向一个实例
providers: [
{provide: NewService, useClass: NewService},
{provide: NewService, useExisting: NewService},
],
工厂Provider
function serviceFactory() { ... }
const provider: FactoryProvider = {provide: 'someToken', useFactory: serviceFactory, deps: []};
useFactory : Function
A function to invoke to create a value for this token. The function is invoked with resolved values of tokens in the deps field.通过条件对输入的多个依赖对象参数进行判断,返回最终依赖的对象
deps : any[]
A list of tokens which need to be resolved by the injector. The list of values is then used as arguments to the useFactory function.用于工厂方法的依赖对象参数
multi : boolean
If true, then 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.
感觉不对劲啊...
以下为官网内容:
注入器的提供商们
提供商提供依赖值的一个具体的、运行时的版本。 注入器依靠提供商创建服务的实例,注入器再将服务的实例注入组件或其它服务。
必须为注入器注册一个服务的提供商,否则它不知道该如何创建该服务。
我们在前面通过AppModule
元数据中的providers
数组注册过Logger
服务,就像这样:
Copy Code
providers: [Logger]
有很多方式可以提供一些实现 Logger
类的东西。 Logger
类本身是一个显而易见而且自然而然的提供商。 但它不是唯一的选项。
可以用其它备选提供商来配置注入器,只要它们能交付一个行为类似于Logger
的对象就可以了。 可以提供一个替代类。你可以提供一个类似日志的对象。 可以给它一个提供商,让它调用可以创建日志服务的工厂函数。 所有这些方法,只要用在正确的场合,都可能是一个好的选择。
最重要的是,当注入器需要一个Logger
时,它得先有一个提供商。
Provider类和一个提供商的字面量
像下面一样写providers
数组:
Copy Code
providers: [Logger]
这其实是用于注册提供商的简写表达式。 使用的是一个带有两个属性的提供商对象字面量:
Copy Code
[{ provide: Logger, useClass: Logger }]
第一个是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。
The first is the token that serves as the key for both locating a dependency value and registering the provider.
第二个是一个提供商定义对象。 可以把它看做是指导如何创建依赖值的配方。 有很多方式创建依赖值…… 也有很多方式可以写配方。
备选的类提供商
某些时候,我们会请求一个不同的类来提供服务。 下列代码告诉注入器,当有人请求Logger
时,返回BetterLogger
。
Copy Code
[{ provide: Logger, useClass: BetterLogger }]
带依赖的类提供商
假设EvenBetterLogger
可以在日志消息中显示用户名。 这个日志服务从注入的UserService
中取得用户, UserService
通常也会在应用级注入。
Copy Code
@Injectable()
class EvenBetterLogger extends Logger {
constructor(private userService: UserService) { super(); }
log(message: string) {
let name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`);
}
}
就像之前在BetterLogger
中那样配置它。
Configure it like BetterLogger
.
Copy Code
[ UserService,
{ provide: Logger, useClass: EvenBetterLogger }]
别名类提供商
假设某个旧组件依赖一个OldLogger
类。 OldLogger
和NewLogger
具有相同的接口,但是由于某些原因, 我们不能升级这个旧组件并使用它。
当旧组件想使用OldLogger
记录消息时,我们希望改用NewLogger
的单例对象来记录。
不管组件请求的是新的还是旧的日志服务,依赖注入器注入的都应该是同一个单例对象。 也就是说,OldLogger
应该是NewLogger
的别名。
我们当然不会希望应用中有两个不同的NewLogger
实例。 不幸的是,如果尝试通过useClass
来把OldLogger
作为NewLogger
的别名,就会导致这样的后果。
Copy Code
[ NewLogger,
// Not aliased! Creates two instances of `NewLogger`
{ provide: OldLogger, useClass: NewLogger}]
解决方案:使用useExisting
选项指定别名。
Copy Code
[ NewLogger,
// Alias OldLogger w/ reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}]
值提供商
有时,提供一个预先做好的对象会比请求注入器从类中创建它更容易。
Copy Code
// An object in the shape of the logger service
let silentLogger = {
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
log: () => {}
};
于是可以通过useValue
选项来注册提供商,它会让这个对象直接扮演 logger 的角色。
Copy Code
[{ provide: Logger, useValue: silentLogger }]
查看更多useValue
的例子,见非类依赖和 InjectionToken部分。
工厂提供商
有时,我们需要动态创建这个依赖值,因为它所需要的信息直到最后一刻才能确定。 也许这个信息会在浏览器的会话中不停地变化。
还假设这个可注入的服务没法通过独立的源访问此信息。
这种情况下,请调用工厂提供商。
下面通过添加新的业务需求来说明这一点: HeroService
必须对普通用户隐藏掉秘密英雄。 只有授权用户才能看到秘密英雄。
就像EvenBetterLogger
那样,HeroService
需要了解此用户的身份。 它需要知道,这个用户是否有权看到隐藏英雄。 这个授权可能在单一的应用会话中被改变,例如,改用另一个用户的身份登录时。
与EvenBetterLogger
不同,不能把UserService
注入到HeroService
中。 HeroService
无权访问用户信息,来决定谁有授权谁没有授权。
让HeroService
的构造函数带上一个布尔型的标志,来控制是否显示隐藏的英雄。
src/app/heroes/hero.service.ts (excerpt)
Copy Code
constructor(
private logger: Logger,
private isAuthorized: boolean) { }
getHeroes() {
let auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}
我们可以注入Logger
,但是不能注入逻辑型的isAuthorized
。 我们不得不通过通过工厂提供商创建这个HeroService
的新实例。
工厂提供商需要一个工厂方法:
src/app/heroes/hero.service.provider.ts (excerpt)
Copy Code
let heroServiceFactory = (logger: Logger, userService: UserService) => {
return new HeroService(logger, userService.user.isAuthorized);
};
虽然HeroService
不能访问UserService
,但是工厂方法可以。
同时把Logger
和UserService
注入到工厂提供商中,并且让注入器把它们传给工厂方法:
src/app/heroes/hero.service.provider.ts (excerpt)
Copy Code
export let heroServiceProvider =
{ provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService]
};
useFactory
字段告诉 Angular:这个提供商是一个工厂方法,它的实现是heroServiceFactory
。
deps
属性是提供商令牌数组。 Logger
和UserService
类作为它们自身类提供商的令牌。 注入器解析这些令牌,把相应的服务注入到工厂函数中相应的参数中去。
注意,我们在一个导出的变量中捕获了这个工厂提供商:heroServiceProvider
。 这个额外的步骤让工厂提供商可被复用。 无论哪里需要,都可以使用这个变量注册HeroService
。
在这个例子中,只在HeroesComponent
中需要它, 这里,它代替了元数据providers
数组中原来的HeroService
注册。 对比一下新的和旧的实现:
src/app/heroes/heroes.component (v3)src/app/heroes/heroes.component (v2)
Copy Code
import { Component } from '@angular/core';
import { heroServiceProvider } from './hero.service.provider';
@Component({
selector: 'my-heroes',
template: `
<h2>Heroes</h2>
<hero-list></hero-list>
`,
providers: [heroServiceProvider]
})
export class HeroesComponent { }
依赖注入令牌
当向注入器注册提供商时,实际上是把这个提供商和一个 DI 令牌关联起来了。 注入器维护一个内部的令牌-提供商映射表,这个映射表会在请求依赖时被引用到。 令牌就是这个映射表中的键值。
在前面的所有例子中,依赖值都是一个类实例,并且类的类型作为它自己的查找键值。 在下面的代码中,HeroService
类型作为令牌,直接从注入器中获取HeroService
实例:
Copy Code
heroService: HeroService;
编写需要基于类的依赖注入的构造函数对我们来说是很幸运的。 只要定义一个HeroService
类型的构造函数参数, Angular 就会知道把跟HeroService
类令牌关联的服务注入进来:
Copy Code
constructor(heroService: HeroService)
这是一个特殊的规约,因为大多数依赖值都是以类的形式提供的。
非类依赖
如果依赖值不是一个类呢?有时候想要注入的东西是一个字符串,函数或者对象。
应用程序经常为很多很小的因素定义配置对象(例如应用程序的标题或网络API终点的地址)。 但是这些配置对象不总是类的实例,它们可能是对象,如下面这个:
src/app/app-config.ts (excerpt)
Copy Code
export interface AppConfig {
apiEndpoint: string;
title: string;
}
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};
我们想让这个配置对象在注入时可用,而且知道可以使用值提供商来注册一个对象。
但是,这种情况下用什么作令牌呢? 我们没办法找一个类来当作令牌,因为没有Config
类。
TypeScript 接口不是一个有效的令牌
CONFIG
常量有一个接口:AppConfig
。不幸的是,不能把 TypeScript 接口用作令牌:
Copy Code
// FAIL! Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
Copy Code
// FAIL! Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }
对于习惯于在强类型的语言中使用依赖注入的开发人员,这会看起来很奇怪, 因为在强类型语言中,接口是首选的用于查找依赖的主键。
这不是 Angular 的错。接口只是 TypeScript 设计时 (design-time) 的概念。JavaScript 没有接口。 TypeScript 接口不会出现在生成的 JavaScript 代码中。 在运行期,没有接口类型信息可供 Angular 查找。
InjectionToken
解决方案是为非类依赖定义和使用InjectionToken作为提供商令牌。 定义方式是这样的:
Copy Code
import { InjectionToken } from '@angular/core';
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
类型参数,虽然是可选的,但可以向开发者和开发工具传达类型信息。 而且这个令牌的描述信息也可以为开发者提供帮助。
使用这个InjectionToken
对象注册依赖的提供商:
Copy Code
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
现在,在@Inject
装饰器的帮助下,这个配置对象可以注入到任何需要它的构造函数中:
Copy Code
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
虽然AppConfig
接口在依赖注入过程中没有任何作用,但它为该类中的配置对象提供了强类型信息。
或者在 ngModule 中提供并注入这个配置对象,如AppModule
。
src/app/app.module.ts (ngmodule-providers)
Copy Code
providers: [
UserService,
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],
可选依赖
HeroService
需要一个Logger
,但是如果想不提供 Logger 也能得到它,该怎么办呢? 可以把构造函数的参数标记为@Optional()
,告诉 Angular 该依赖是可选的:
Copy Code
import { Optional } from '@angular/core';
Copy Code
constructor(@Optional() private logger: Logger) {
if (this.logger) {
this.logger.log(some_message);
}
}
当使用@Optional()
时,代码必须准备好如何处理空值。 如果其它的代码没有注册一个 logger
,注入器会设置该logger
的值为空 null。