# 配置依赖提供者

“创建与注入服务”这个主题介绍的是如何使用类作为依赖项。除了类之外,你还可以用其他值作为依赖项,例如 Boolean、字符串、日期和对象。 Angular DI 提供了一些必要的 API 来让依赖的配置方式更加灵活,以便你可以把这些值在 DI 中可用。

# 指定提供者令牌

如果你用服务类作为提供者令牌,则其默认行为是注入器使用 new 运算符实例化该类。

在下面这个例子中,Logger 类提供了 Logger 的实例。

providers: [Logger]

但是,你可以将 DI 配置为使用不同的类或任何其他不同的值来与 Logger 类关联。因此,当注入 Logger 时,会改为使用这个新值。

实际上,类提供者语法是一个简写表达式,可以扩展为由 Provider 接口定义的提供者配置信息。

在这种情况下,Angular 将 providers 值展开为完整的提供者对象,如下所示:

[{ provide: Logger, useClass: Logger }]

展开后的提供者配置是一个具有两个属性的对象字面量:

  • provide 属性包含一个令牌,该令牌会作为定位依赖值和配置注入器时的键。
  • 第二个属性是一个提供者定义对象,它会告诉注入器如何创建依赖值。 提供者定义对象中的键可以是以下值之一:
    • useClass -此选项告诉 Angular DI 在注入依赖项时要实例化这里提供的类
    • useExisting - 允许你为令牌起一个别名,并引用任意一个现有令牌。
    • useFactory - 允许你定义一个用来构造依赖项的函数。
    • useValue - 提供了一个应该作为依赖项使用的静态值。

下面的部分介绍如何使用这里所说的“提供者定义”键。

# 类提供者:useClass

useClass 这个提供者键名能让你创建并返回指定类的新实例。你可以用这种类型的提供者来作为通用类或默认类的替代实现。例如,替代实现可以实现不同的策略、扩展默认类或模拟测试用例中真实类的行为。在以下示例中,当在组件或任何其他类中请求 Logger 依赖项时,将转而实例化 BetterLogger 类。

[{ provide: Logger, useClass: BetterLogger }]

如果替代类提供者有自己的依赖项,请在父模块或组件的 providers 元数据属性中指定这两个提供者。

content_copy
[ UserService,
  { provide: Logger, useClass: EvenBetterLogger }]

在这个例子中,EvenBetterLogger 会在日志信息里显示用户名。这个 logger 要从注入的 UserService 实例中来获取该用户。

@Injectable()
export class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  override log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

Angular DI 知道如何构建 UserService 依赖项,因为它已经在上面进行配置并且在注入器中可用。

# 别名提供者:useExisting

useExisting 提供者键允许你将一个令牌映射到另一个。实际上,第一个令牌是与第二个令牌关联的服务的别名,创建了两种访问同一个服务对象的方式。

在下面的例子中,当组件请求新的或旧的记录器时,注入器都会注入一个 NewLogger 的实例。通过这种方式,OldLogger 就成了 NewLogger 的别名。

[ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]

确保你没有使用 OldLoggerNewLogger 别名为 useClass ,因为这会创建两个不同 NewLogger 实例。

# 工厂提供者:useFactory

useFactory 提供者键允许你通过调用工厂函数来创建依赖对象。使用这种方法,你可以根据 DI 和应用程序中其他地方的可用信息创建动态值。

在下面的例子中,只有授权用户才能看到 HeroService 中的秘密英雄。授权可能在单个应用会话期间发生变化,比如改用其他用户登录。

要想在 UserServiceHeroService 中保存敏感信息,就要给 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);
}

要实现 isAuthorized 标志,可以用工厂提供者来为 HeroService 创建一个新的 logger 实例。

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

const heroServiceFactory = (logger: Logger, userService: UserService) =>
  new HeroService(logger, userService.user.isAuthorized);

这个工厂函数可以访问 UserService。你可以同时把 LoggerUserService 注入到工厂提供者中,这样注入器就可以把它们传给工厂函数了。

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

export const heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };
  • useFactory 字段指定该提供者是一个工厂函数,其实现代码是 heroServiceFactory
  • deps 属性是一个提供者令牌的数组。 LoggerUserService 类作为它们自己的类提供者的令牌。注入器会解析这些令牌,并将相应的服务注入到匹配的 heroServiceFactory 工厂函数参数中。

通过把工厂提供者导出为变量 heroServiceProvider,就能让工厂提供者变得可复用。

# 值提供者:useValue

useValue 键允许你将固定值与某个 DI 令牌相关联。可以用此技术提供运行时配置常量,例如网站基址和特性标志。你还可以在单元测试中使用值提供者来提供模拟数据以代替生产级数据服务。下一节提供有关 useValue 键的更多信息。

# 使用 InjectionToken 对象

可以定义和使用一个 InjectionToken 对象来为非类的依赖选择一个提供者令牌。下列例子定义了一个类型为 InjectionTokenAPP_CONFIG

src/app/app.config.ts

import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

可选的参数 `` 和令牌描述 app.config 指明了此令牌的用途。

接着,用 APP_CONFIG 这个 InjectionToken 对象在组件中注册依赖提供者。

src/app/providers.component.ts

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

现在,借助参数装饰器 @Inject(),你可以把这个配置对象注入到构造函数中。

src/app/app.component.ts

constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

# 接口和 DI

尽管 TypeScript 的 AppConfig 接口可以充当类的类型,但 AppConfig 接口在 DI 中无法使用。在 TypeScript 中,接口是设计时工件,它没有可供 DI 框架使用的运行时表示或令牌。

当转译器把 TypeScript 转换成 JavaScript 时,接口就会消失,因为 JavaScript 没有接口。

由于 Angular 在运行期没有接口,所以该接口不能作为令牌,也不能注入它。

// 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){ }

# 下一步呢?

Last Updated: 5/10/2023, 8:25:49 AM