提供者
提供者是Nest中的一个基本概念。 许多基本的Nest类可以被视为提供者:
- 服务(services)
- 存储库(repositories)
- 工厂(factories)
- 助手(helpers)
许多基本的Nest类可以被视为提供者-服务、存储库、工厂、助手等等。 提供者的主要思想是它可以作为依赖项注入。 这意味着对象之间可以创建各种关系, 并且“连接”这些对象的功能很大程度上可以委托给 Nest运行时系统。
在上一章节中,我们构建了一个简单的CatsController
。
控制器应该处理HTTP请求并将更复杂的任务委托给提供者。
提供者在模块中声明为providers
的纯JavaScript类。
由于Nest能够以更加面向对象的方式设计和组织依赖关系,因此我们强烈建议遵循SOLID原则
服务
让我们创建一个简单的CatsService
开始。
这个服务将负责数据存储和检索,并且设计为由CatsController
使用,
因此它是定义为提供者的良好候选者。
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
要使用CLI创建服务,只虚执行nest g service cats
命令
我们的CatsService
事一个基本类,具有一个属性和两个方法。
唯一的新功能是它使用@Injectable
装饰器。
@Injectable()
装饰器附加元数据,
它声明CatsService
是一个可以由Nest IoC容器管理的类。
顺便说一句,这个例子也使用了Cat
接口,它可能看起来像这样。
export interface Cat {
name: string;
age: number;
breed: string;
}
现在我们有了一个用于检索猫的服务类,让我们在CatsController
中使用它:
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
CatsService
通过类构造函数注入。
请注意私有语法的使用。这种简写允许我们在同一位置立即声明和初始化catsService
成员。
依赖注入
Nest是围绕通常称为依赖注入的强大设计模式构建的。 我们建议阅读Angular 官方文档中有关此概念的精彩文章。
在Nest中,借助TypeScript功能,管理依赖项变得非常容易,因为它们仅通过类型来解析。
在下面的示例中,Nest将通过创建并返回catsService
(或者,在单例的正常情况下,如果已在
其他地方请求了 现有实例,则返回现有实例)。
此依赖性已解决并传递给控制器的构造函数(或分配给指定的属性)。
constructor(private catsService: CatsService) {}
作用域
提供者通常具有与应用程序生命周期同步的生命周期("scope")。 当应用程序启动时,必须解决每个依赖项,因此必须实例化每个提供者程序。 同样,当应用程序关闭时,每个提供者程序都将被销毁。 但是,也有一些方法可以使您的提供者程序生命周期限定在请求范围内。
自定义提供者
Nest有一个内置的控制反转(IoC)容器, 可以解析提供者之间的关系。 此功能是上述依赖注入功能的基础, 但实际上比我们迄今为止所描述的功能要强大得多。 有多种方法可以定义提供者程序:您可以使用普通值、类以及异步或同步工厂。
可选提供者
有时,您可能存在不一定需要解决的依赖关系。 例如,您的类可能依赖于配置对象,但如果没有传递任何内容,则应使用默认值。 在这种情况下,依赖关系变得可选,因为缺少配 置提供者程序不会导致错误。
要指明提供者程序时可选的,请在构造函数的签名中使用@Optional()
装饰器。
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HtpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
请注意,在上面的示例中,我们使用的事自定义提供者程序,
这就是我们包含HTTP_OPTIONS
自定义符号的原因。
前面的示例显示了基于构造函数的注入,指示通过构造函数中的类的依赖关系。
基于属性的注入
到目前为止,我们使用的技术称为基于构造函数的注入,
因为提供者程序是通过构造函数方法注入的。
在某些非常特殊的情况下,基于属性的注入可能很有用。例如,如果您的顶级类依赖于一个或多个提供者,
则通过从构造函数调用子类中的super()
将它们一路向上传递可能会非常乏味。
为了避免这种情况,您可以再属性级别使用@Inject()
装饰器。
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS')
private readonly httpClient: T;
}
如果您的类没有扩展另一个类,那么您应该始终更喜欢使用基于构造函数的注入。
提供者注册
现在我们已经定义了一个提供者(CatsService
),并且有了该服务的使用者(CatsController
),
我们需要向Nest注册该服务,以便它可以执行注入。
我们通过编辑模块文件(app.module.ts
)
并将服务添加到@Module()
装饰器的提供者数组中来实 现此目的。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
Nest现在能够解析CatsController
类的依赖关系。
这就是我们的目录结构现在的样子:
src
|--cats
|--dto
|--create-cat.dto.ts
|--interfaces
|--cat.interface.ts
|--cats.controller.ts
|--cats.service.ts
|--app.module.ts
|--main.ts
手动实例化
到目前为止,我们已经讨论了Nest如何自动处理解决依赖关系的大部分细节。 在某些情况下,您可能需要跳出内置依赖注入系统并手动检索或实例化提供者程序。
要获取现有实例或动态实例化提供者程序,您可以使用模块引用。
在bootstrap()
函数中获取提供者程序(例如,对于没有控制器的独立应用程序,
或在引导期间使用配置服务)。