提供者
提供者是 Nest 中的核心概念。许多基本的 Nest 类,如服务、仓库、工厂和辅助类,都可以被视为提供者。提供者的关键思想是它可以作为依赖项被注入,允许对象之间形成各种关系。"连接"这些对象的职责主要由 Nest 运行时系统处理。

在上一章中,我们创建了一个简单的 CatsController。控制器应该处理 HTTP 请求,并将更复杂的任务委托给提供者。提供者是在 NestJS 模块中声明为 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() 装饰器。这个装饰器将元数据附加到类上,表明 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 通过类构造函数注入。注意 private 关键字的使用。这个简写允许我们在同一行中声明和初始化 catsService 成员,简化了流程。
依赖注入
Nest 围绕强大的设计模式——依赖注入构建。我们强烈建议阅读 Angular 官方文档中关于这个概念的精彩文章。
在 Nest 中,得益于 TypeScript 的能力,管理依赖非常简单,因为它们是基于类型解析的。在下面的示例中,Nest 将通过创建并返回 CatsService 的实例来解析 catsService(或者,在单例的情况下,如果已在其他地方请求过,则返回现有实例)。然后将此依赖注入到控制器的构造函数中(或分配给指定的属性):
constructor(private catsService: CatsService) {}作用域
提供者通常具有与应用生命周期一致的生命周期("作用域")。当应用启动时,每个依赖都必须被解析,这意味着每个提供者都会被实例化。类似地,当应用关闭时,所有提供者都会被销毁。然而,也可以将提供者设为请求作用域,这意味着其生命周期与特定请求绑定,而不是与应用的生命周期绑定。你可以在注入作用域章节中了解更多关于这些技术的信息。
自定义提供者
Nest 自带一个内置的控制反转("IoC")容器,用于管理提供者之间的关系。这个功能是依赖注入的基础,但实际上它比我们目前介绍的要强大得多。定义提供者有几种方式:你可以使用普通值、类,以及异步或同步工厂。更多定义提供者的示例,请查看依赖注入章节。
可选提供者
有时,你可能有不总是需要被解析的依赖。例如,你的类可能依赖于一个配置对象,但如果没有提供,则应使用默认值。在这种情况下,依赖被视为可选的,配置提供者的缺失不应导致错误。
要将提供者标记为可选,请在构造函数签名中使用 @Optional() 装饰器。
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<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;
}警告
如果你的类没有继承其他类,通常最好使用基于构造函数的注入。构造函数清楚地指定了需要哪些依赖,提供了更好的可见性,使代码比使用 @Inject 注解的类属性更容易理解。
提供者注册
现在我们已经定义了一个提供者(CatsService)和一个消费者(CatsController),我们需要将服务注册到 Nest 中,以便它可以处理注入。这通过编辑模块文件(app.module.ts)并将服务添加到 @Module() 装饰器的 providers 数组中来完成。
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 如何自动处理解析依赖的大部分细节。然而,在某些情况下,你可能需要跳出内置的依赖注入系统,手动检索或实例化提供者。下面简要讨论两种这样的技术。