Angular注册Provider

这里主要讲一下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类。 OldLoggerNewLogger具有相同的接口,但是由于某些原因, 我们不能升级这个旧组件并使用它。

组件想使用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,但是工厂方法可以。

同时把LoggerUserService注入到工厂提供商中,并且让注入器把它们传给工厂方法:

src/app/heroes/hero.service.provider.ts (excerpt)

Copy Code

export let heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };

useFactory字段告诉 Angular:这个提供商是一个工厂方法,它的实现是heroServiceFactory

deps属性是提供商令牌数组。 LoggerUserService类作为它们自身类提供商的令牌。 注入器解析这些令牌,把相应的服务注入到工厂函数中相应的参数中去。

注意,我们在一个导出的变量中捕获了这个工厂提供商:heroServiceProvider。 这个额外的步骤让工厂提供商可被复用。 无论哪里需要,都可以使用这个变量注册HeroService

在这个例子中,只在HeroesComponent中需要它, 这里,它代替了元数据providers数组中原来的HeroService注册。 对比一下新的和旧的实现:

src/app/heroes/heroes.component (v3)src/app/heroes/heroes.component (v2)

Copy Code

 
  1. import { Component } from '@angular/core';
  2.  
  3. import { heroServiceProvider } from './hero.service.provider';
  4.  
  5. @Component({
  6. selector: 'my-heroes',
  7. template: `
  8. <h2>Heroes</h2>
  9. <hero-list></hero-list>
  10. `,
  11. providers: [heroServiceProvider]
  12. })
  13. 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。

 

转载于:https://my.oschina.net/u/3412211/blog/898672

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值