nest 自定义提供者

时间:2023-01-12 浏览:184 分类:NestJS

标准提供者(Provider)

我们仔细看看 @Module() 装饰器。在 user.module 中,我们声明:

@Module({
  controllers: [UserController],
  providers: [UserService],
})

providers 属性采用数组的形式提供。到目前为止,我们已经通过类名列表提供了这些提供者。事实上,语法 providers: [UserService] 是更完整语法的简写如下:

providers: [
  {
    provide: UserService,
    useClass: UserService,
  },
];

既然我们看到了这个明确的结构,我们就可以理解注册过程了。在这里,我们明确地将令牌 UserService 与类 UserService 相关联。简写符号只是为了简化最常见的用例,其中令牌provider 用于请求具有相同名称的类的实例。

自定义提供者

如果要求超出标准提供者时会发生什么?这里有一些例子:

  • 想要创建一个自定义实例而不是让 Nest 实例化(或返回一个类的缓存实例)

  • 想在第二个依赖项中重用现有的类

  • 想用模拟版本覆盖一个类以进行测试

Nest 允许定义自定义提供者来处理这些情况。它提供了几种定义自定义提供者的方法。如下:

第一:userValue

useValue 语法对于注入常量值、将外部库放入 Nest 容器或用模拟对象替换真实实现非常有用。假设您想强制 Nest 使用模拟的 CatsService 进行测试。

import { CatsService } from './cats.service';

const mockCatsService = {
  /* mock implementation
  ...
  */
};

@Module({
  imports: [CatsModule],
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

在此示例中,CatsService 令牌将解析为 mockCatsService 模拟对象。 useValue 需要一个值——在本例中是一个文字对象,它与它要替换的 CatsService 类具有相同的接口。由于 TypeScript 的结构类型,可以使用任何具有兼容接口的对象,包括文字对象或用 new 实例化的类实例。

第二:useClass

除了上述的例子,如果useClass还能动态的解析令牌所指向的类,比如:

const configServiceProvider = {
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === 'development'
      ? DevelopmentConfigService
      : ProductionConfigService,
};

@Module({
  providers: [configServiceProvider],
})
export class AppModule {}

可以看到,configServiceProvider最后指定的类是动态的被指定,可以是DevelopmentConfigService ,也可以是ProductionConfigService。

第三:非class 

有时,我们可能希望灵活地使用字符串或符号作为 DI 令牌:

import { connection } from './connection';

@Module({
  providers: [
    {
      provide: 'CONNECTION',
      useValue: connection,
    },
  ],
})
export class AppModule {}

在此示例中,我们将字符串值标记 ('CONNECTION') 与我们从外部文件导入的预先存在的连接对象相关联。

第四:useFactory

useFactory 允许动态创建提供者。实际提供者将由工厂函数返回的值提供。工厂函数可以根据需要简单或复杂。一个简单的工厂可能不依赖于任何其他提供者。更复杂的工厂本身可以注入所需的其他提供者的结果。对于后一种情况,工厂提供者语法有一对相关的机制:

a. 工厂函数可以接受(可选)参数。

b. inject 属性(可选的)接受一组提供者,Nest 将在实例化过程中解析这些提供者并将其作为参数传递给工厂函数。此外,这些提供者可以标记为可选。这两个列表应该是相关的:Nest 将以相同的顺序将注入列表中的实例作为参数传递给工厂函数。下面的示例演示了这一点。

const connectionProvider = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
  //       \_____________/            \__________________/
  //        This provider              The provider with this
  //        is mandatory.              token can resolve to `undefined`.
};

@Module({
  providers: [
    connectionProvider,
    OptionsProvider,
    // { provide: 'SomeOptionalProvider', useValue: 'anything' },
  ],
})
export class AppModule {}

第五:useExisting

useExisting 允许为现有提供者创建别名。

@Injectable()
class LoggerService {
  /* implementation details */
}

const loggerAliasProvider = {
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};

@Module({
  providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}

异步提供者

有时,应延迟应用程序启动,直到完成一个或多个异步任务。例如,可能不想在与数据库建立连接之前开始接受请求。可以使用异步提供程序实现此目的。

{
  provide: 'ASYNC_CONNECTION',
  useFactory: async () => {
    const connection = await createConnection(options);
    return connection;
  },
}


总结一下:所有服务Service 或者提供者Providers 都应该用@Injectable() 修饰标注,表示自己可以作为依赖被注入到其他的类中,这样才能在module 注册入Ioc容器中。