Module是一个带有@Module()
装饰器的类。@Module()
装饰器提供了Nest用来组织应用程序结构的元数据。
@Module()
装饰器接受一个对象,该对象的属性描述了该模块:
Properties | Description |
---|---|
Providers | 这些提供商将被Nest注入器实例化,并且至少可以在整个模块中共享 |
controllers | 在此模块中定义的控制器集,必须实例化 |
imports | 导出该模块中所需的provider的导入模块列表 |
exports | 提供程序的子集,由该模块提供,并且应该在导入该模块的其他模块中可用。您既可以使用提供者本身,也可以只使用它的令牌(提供值) |
该模块默认封装provider。这意味着不可能注入既不是当前模块的直接组成部分,也不是从导入模块导出的provider。因此,您可以将从模块导出的provider视为模块的公共接口或API。
Feature modules
CatsController
和CatsService
属于同一个应用程序域。由于它们密切相关,因此将它们移到特性模块中是有意义的。功能模块只是组织与特定功能相关的代码,保持代码的组织性并建立清晰的边界。这有助于我们管理复杂性,并使用SOLID原则进行开发,特别是当应用程序和/或团队的规模增长时。
为了演示这一点,我们将创建CatsModule
。
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
[!hint]
要使用CLI创建一个模块,只需执行$ nest g module cats
命令。
上面,我们在cats.module.ts
文件中定义了CatsModule
,并将与该模块相关的所有内容移动到cats
目录中。我们需要做的最后一件事是将这个模块导入根模块(AppModule
,定义在app.module.ts
文件中)。
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
下面是我们的目录结构:
Shared modules
在Nest中,模块默认是单例的,因此你可以毫不费力地在多个模块之间共享任何提供者的相同实例。
每个模块自动成为一个共享模块。一旦创建,它就可以被任何模块重用。假设我们想要在其他几个模块之间共享CatsService
的一个实例。为了做到这一点,我们首先需要通过将其添加到模块的exports
数组中来导出CatsService
提供商,如下所示:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
现在,任何导入CatsModule
的模块都可以访问CatsService
,并将与导入它的所有其他模块共享同一个实例。
Module re-exporting
如上所述,模块可以导出它们的内部provider。此外,它们还可以重新导出它们导入的模块。在下面的例子中,CommonModule
被导入CoreModule
并从CoreModule
中导出,使得它可以被其他导入这个模块的模块使用。
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
Dependency injection
模块类也可以注入provider(例如,用于配置目的):
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}
然而,由于循环依赖,模块类本身不能作为provider注入。
Global modules
如果必须在任何地方导入相同的模块集,就会变得很繁琐。与Nest不同,angular的providers是在全局作用域中注册的。一旦定义,它们就随处可见。然而,Nest将提供程序封装在模块作用域中。如果不先导入封装模块,就不能在其他地方使用模块的提供程序。
当你想要提供一组在任何地方都可以使用的提供程序(例如,帮助程序、数据库连接等)时,使用@Global()
装饰器将模块设置为全局。
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
@Global()
装饰器使模块成为全局作用域。全局模块应该只注册一次,通常由根模块或核心模块注册。在上面的例子中,CatsService
提供者将是无处不在的,并且希望注入该服务的模块将不需要在其imports数组中导入CatsModule
。
[!hint]
让所有东西都是全局的并不是一个好的设计决策。可以使用全局模块来减少必要的样板文件的数量。imports
数组通常是让消费者可以使用模块API的首选方式。
Dynamic modules
Nest模块系统包括一个强大的功能,称为动态模块。此特性使您能够轻松地创建可定制的模块,这些模块可以动态地注册和配置提供程序。动态模块在这里有广泛的介绍。在本章中,我们将给出一个简短的概述来完成对模块的介绍。
下面是一个DatabaseModule
动态模块定义的例子:
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
[!hint]
forRoot()
方法可以同步或异步(即通过Promise
)返回一个动态模块。
该模块在默认情况下定义了Connection
provider(在@Module()
装饰器元数据中),但另外——取决于传递给forRoot()
方法的entities
和options
对象——还公开了一个提供程序集合,例如存储库。注意,动态模块返回的属性扩展(而不是覆盖)了在@Module()
装饰器中定义的基本模块元数据。这就是从模块导出静态声明的Connection
提供程序和动态生成的存储库提供程序的方式。
如果要在全局作用域中注册一个动态模块,请将global
属性设置为true
。
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
[!warning]
正如上面所提到的,让所有东西都是全局的并不是一个好的设计决策。
DatabaseModule可以通过以下方式导入和配置:
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
如果你想反过来重新导出一个动态模块,你可以忽略exports数组中的forRoot()方法调用:
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class AppModule {}
[!hint]
在本章中,学习如何使用ConfigurableModuleBuilder
构建高度可定制的动态模块。