教程 > nest.js 中文教程 > 阅读:770

nest.js 动态模块——迹忆客-ag捕鱼王app官网

模型一章介绍了 nest 模型的基础知识,并简要介绍了动态模型。 本章对动态模型的进行扩展。 完成后,我们应该很好地掌握它们是什么以及何时并且如何使用它们。

介绍

文档概述部分中的大多数应用程序代码示例都使用常规或静态模块。 模块定义了组件组,例如提供者和控制器,它们作为整个应用程序的模块化部分组合在一起。 它们为这些组件提供执行上下文或范围。 例如,模块中定义的提供程序对模块的其他成员可见,而无需导出它们。 当一个提供者需要在模块之外可见时,它首先从它的宿主模块中导出,然后导入到它的消费模块中。

让我们来看一个熟悉的例子。

首先,我们将定义一个 usersmodule 来提供和导出一个 usersservice。 usersmodule 是 usersservice 的宿主模块。

import { module } from '@nestjs/common';
import { usersservice } from './users.service';
@module({
  providers: [usersservice],
  exports: [usersservice],
})
export class usersmodule {}

接下来,我们将定义一个 authmodule,它导入 usersmodule,使 usersmodule 的导出提供者在 authmodule 中可用:

import { module } from '@nestjs/common';
import { authservice } from './auth.service';
import { usersmodule } from '../users/users.module';
@module({
  imports: [usersmodule],
  providers: [authservice],
  exports: [authservice],
})
export class authmodule {}

这些构造允许我们将 usersservice 注入到,例如 authmodule 中托管的 authservice 中:

import { injectable } from '@nestjs/common';
import { usersservice } from '../users/users.service';
@injectable()
export class authservice {
  constructor(private usersservice: usersservice) {}
  /*
    使用 this.usersservice 的实现
  */
}

我们将其称为静态模块绑定。 nest 需要将模块连接在一起的所有信息都已在主机和消费模块中声明。 让我们分析一下这个过程中发生的事情。 nest 通过以下方式使用户服务在 authmodule 中可用:

  1. 实例化 usersmodule,包括传递性地导入 usersmodule 本身使用的其他模块,以及传递性地解决任何依赖关系(请参阅自定义提供程序)。
  2. 实例化 authmodule,并使 usersmodule 的导出提供者可用于 authmodule 中的组件(就像它们已在 authmodule 中声明一样)。
  3. 在 authservice 中注入 usersservice 的实例。

动态模型用例

使用静态模块绑定,消费模块没有机会影响主机模块提供者的配置方式。为什么这很重要?考虑我们有一个通用模块需要在不同用例中表现不同的情况。这类似于许多系统中的“插件”概念,其中通用设施需要一些配置才能被消费者使用。

nest 的一个很好的例子是配置模块。许多应用程序发现使用配置模块将配置细节外部化很有用。这使得在不同部署中动态更改应用程序设置变得容易:例如,开发人员的开发数据库,暂存/测试环境的暂存数据库等。通过将配置参数的管理委托给配置模块,应用程序源代码独立于配置参数。

挑战在于配置模块本身,因为它是通用的(类似于“插件”),需要通过其消费模块进行定制。这就是动态模块发挥作用的地方。使用动态模块特性,我们可以使我们的配置模块动态化,以便使用模块可以使用 api 来控制配置模块在导入时如何定制。

换句话说,动态模块提供了一个 api,用于将一个模块导入另一个模块,并在导入该模块时自定义该模块的属性和行为,而不是使用我们目前看到的静态绑定。


配置模块示例

本章末尾可获得示例的完整代码。

我们的要求是让 configmodule 接受一个 options 对象来自定义它。这是我们想要支持的功能。基本示例将 .env 文件的位置硬编码到项目根文件夹中。假设我们想要使其可配置,这样我们就可以在选择的任何文件夹中管理自己的 .env 文件。例如,假设我们想将各种 .env 文件存储在项目根目录下名为 config 的文件夹中(即 src 的同级文件夹)。在不同项目中使用 configmodule 时,我们希望能够选择不同的文件夹。

动态模块使我们能够将参数传递给正在导入的模块,以便我们可以更改其行为。让我们看看这是如何工作的。如果我们从消费模块的角度来看这可能看起来如何的最终目标开始,然后向后执行,这将很有帮助。首先,让我们快速回顾一下静态导入 configmodule 的示例(即,一种无法影响导入模块行为的方法)。密切注意 @module() 装饰器中的 imports 数组:

import { module } from '@nestjs/common';
import { appcontroller } from './app.controller';
import { appservice } from './app.service';
import { configmodule } from './config/config.module';
@module({
  imports: [configmodule],
  controllers: [appcontroller],
  providers: [appservice],
})
export class appmodule {}

让我们考虑一下动态模块导入(我们在其中传递配置对象)可能是什么样子。 比较这两个示例之间 imports 数组的差异:

import { module } from '@nestjs/common';
import { appcontroller } from './app.controller';
import { appservice } from './app.service';
import { configmodule } from './config/config.module';
@module({
  imports: [configmodule.register({ folder: './config' })],
  controllers: [appcontroller],
  providers: [appservice],
})
export class appmodule {}

让我们看看上面的示例中发生了什么。

  1. configmodule 是一个普通的类,所以我们可以推断它肯定有一个静态方法,叫做 register()。 我们知道它是静态的,因为我们在 configmodule 类上调用它,而不是在类的实例上。 注意:我们将很快创建的这个方法可以具有任意名称,但按照惯例,我们应该将其命名为 forroot()register()
  2. register() 方法由我们定义,因此我们可以接受任何我们喜欢的输入参数。 在这种情况下,我们将接受一个具有合适属性的简单选项对象,这是典型的情况。
  3. 我们可以推断 register() 方法必须返回类似于模块的东西,因为它的返回值出现在熟悉的 imports 列表中,到目前为止我们已经看到包含了一个模块列表。

实际上,我们的 register() 方法会返回一个 dynamicmodule。 动态模块只不过是在运行时创建的模块,具有与静态模块完全相同的属性,加上一个称为 module 的附加属性。 让我们快速回顾一个静态模块声明示例,密切注意传递给装饰器的模块选项:

@module({
  imports: [dogsmodule],
  controllers: [catscontroller],
  providers: [catsservice],
  exports: [catsservice]
})

动态模块必须返回一个具有完全相同接口的对象,外加一个名为 module 的附加属性。 module 属性作为模块的名称,应与模块的类名相同,如下例所示。

提示 :对于动态模块,module 选项对象的所有属性都是可选的,除了 module 。

静态 register() 方法呢? 我们现在可以看到它的工作是返回一个具有 dynamicmodule 接口的对象。 当我们调用它时,我们实际上是在为 imports 列表提供一个模块,类似于我们在静态情况下通过列出模块类名来这样做的方式。 换句话说,动态模块 api 只是返回一个模块,而不是修复 @module 装饰器中的属性,我们以编程方式指定它们。

还有一些细节需要介绍:

  1. 我们现在可以声明 @module() 装饰器的 imports 属性不仅可以采用模块类名称(例如,imports:[usersmodule]),还可以采用返回动态模块的函数(例如,imports:[configmodule.register(. ..)])。
  2. 动态模块本身可以导入其他模块。 在这个例子中我们不会这样做,但是如果动态模块依赖于其他模块的提供者,你可以使用可选的 imports 属性来导入它们。 同样,这与使用 @module() 装饰器为静态模块声明元数据的方式完全相同。

有了这些理解,我们现在可以看看我们的动态 configmodule 声明必须是什么样子。

import { dynamicmodule, module } from '@nestjs/common';
import { configservice } from './config.service';
@module({})
export class configmodule {
  static register(): dynamicmodule {
    return {
      module: configmodule,
      providers: [configservice],
      exports: [configservice],
    };
  }
}

现在应该清楚这些部分是如何联系在一起的。 调用 configmodule.register(...) 返回一个 dynamicmodule 对象,其属性与迄今为止我们通过 @module() 装饰器作为元数据提供的属性基本相同。

提示 : 从@nestjs/common 导入 dynamicmodule。


模块配置

自定义 configmodule 行为的明显ag捕鱼王app官网的解决方案是在静态 register() 方法中传递一个 options 对象,正如我们上面所猜测的。 让我们再看看我们的消费模块的 imports 属性:

import { module } from '@nestjs/common';
import { appcontroller } from './app.controller';
import { appservice } from './app.service';
import { configmodule } from './config/config.module';
@module({
  imports: [configmodule.register({ folder: './config' })],
  controllers: [appcontroller],
  providers: [appservice],
})
export class appmodule {}

这很好地处理了将 options 对象传递给我们的动态模块。 那么我们如何在 configmodule 中使用该对象呢? 让我们考虑一下。 我们知道我们的 configmodule 基本上是一个提供和导出可注入服务的主机 - configservice - 供其他提供者使用。 实际上是我们的 configservice 需要读取 options 对象来自定义其行为。 让我们暂时假设我们知道如何以某种方式从 register() 方法中获取选项到 configservice 中。 有了这个假设,我们可以对服务进行一些更改,从而根据 options 对象的属性自定义其行为。 (注意:目前,由于我们还没有真正确定如何传递它,我们只是硬编码选项。我们会在一分钟内解决这个问题)。

import { injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import { envconfig } from './interfaces';
@injectable()
export class configservice {
  private readonly envconfig: envconfig;
  constructor() {
    const options = { folder: './config' };
    const filepath = `${process.env.node_env || 'development'}.env`;
    const envfile = path.resolve(__dirname, '../../', options.folder, filepath);
    this.envconfig = dotenv.parse(fs.readfilesync(envfile));
  }
  get(key: string): string {
    return this.envconfig[key];
  }
}

现在我们的 configservice 知道如何在我们在 options 中指定的文件夹中找到 .env 文件。

我们剩下的任务是以某种方式将 register() 步骤中的 options 对象注入到我们的 configservice 中。当然,我们将使用依赖注入来做到这一点。这是一个关键点,所以请确保已经理解了它。我们的 configmodule 提供 configservice。 configservice 又依赖于仅在运行时提供的 options 对象。因此,在运行时,我们需要首先将 options 对象绑定到 nest ioc 容器,然后让 nest 将其注入到我们的 configservice 中。请记住,在自定义提供者一章中,提供者可以包含任何值,而不仅仅是服务,因此我们可以使用依赖注入来处理简单的 options 对象。

让我们首先解决将 options 对象绑定到 ioc 容器的问题。我们在静态 register() 方法中执行此操作。请记住,我们正在动态构建模块,模块的属性之一是它的 providers 列表。所以我们需要做的就是将我们的 options 对象定义为提供者。这将使它可以注入到 configservice 中,我们将在下一步中利用它。在下面的代码中,注意 providers 数组:

import { dynamicmodule, module } from '@nestjs/common';
import { configservice } from './config.service';
@module({})
export class configmodule {
  static register(options): dynamicmodule {
    return {
      module: configmodule,
      providers: [
        {
          provide: 'config_options',
          usevalue: options,
        },
        configservice,
      ],
      exports: [configservice],
    };
  }
}

现在我们可以通过将 'config_options' 提供程序注入 configservice 来完成该过程。 回想一下,当我们使用非类令牌定义提供者时,我们需要使用这里描述的 @inject() 装饰器。

import * as dotenv from 'dotenv';
import * as fs from 'fs';
import { injectable, inject } from '@nestjs/common';
import { envconfig } from './interfaces';
@injectable()
export class configservice {
  private readonly envconfig: envconfig;
  constructor(@inject('config_options') private options) {
    const filepath = `${process.env.node_env || 'development'}.env`;
    const envfile = path.resolve(__dirname, '../../', options.folder, filepath);
    this.envconfig = dotenv.parse(fs.readfilesync(envfile));
  }
  get(key: string): string {
    return this.envconfig[key];
  }
}

最后一点:为简单起见,我们在上面使用了基于字符串的注入令牌('config_options'),但最佳实践是将其定义为单独文件中的常量(或符号),然后导入该文件。 例如:

export const config_options = 'config_options';

完整示例

查看笔记

扫码一下
查看教程更方便
网站地图