如何在动态 Nestjs 模块中导入 registerAsync?

Jóh*_*erø 7 import module dynamic typescript nestjs

一直在使用 Advanced NestJS 研究动态模块:如何构建完全动态的 NestJS 模块。

据我所知,大多数人使用本指南来构建同步/异步动态模块。

但是我的问题是,如果我使用 registerAsync 方法,并且我的动态模块需要导入 HttpModule,而 HttpModule 的 register-options 是由我的动态模块提供的。

如何在动态模块中导入模块,动态模块提供选项?或者是处理这个问题的错误方法?如果是这样,你会如何构建它?


这是代码。这实际上是本教程的副本。正如您在 register 方法中看到的那样,它很简单 - 我只是传入选项。registerAsync 但是,我无法弄清楚该怎么做。


任何帮助深表感谢 :)

import { Module, DynamicModule, Provider, HttpModule } from "@nestjs/common";
import { InvoicesHealth } from "./invoices/invoices.health";
import { InvoicesResolver, InvoicesService } from "./invoices";
import {
  CustomerInvoicesOptions,
  CustomerInvoicesAsyncOptions,
  CustomerInvoicesOptionsFactory,
} from "./interfaces";
import { CUSTOMER_INVOICES_OPTIONS } from "./constants";
import { createCustomerInvoicesProviders } from "./providers/customer-invoices.providers";

@Module({
  imports: [],
  controllers: [],
  providers: [InvoicesHealth, InvoicesResolver, InvoicesService],
  exports: [InvoicesHealth],
})
export class CustomerInvoicesModule {
  /**
   * Registers a configured customer-invoices Module for import into the current module
   */
  public static register(options: CustomerInvoicesOptions): DynamicModule {
    return {
      imports: [
        HttpModule.register({
          url: options.url,
          auth: {
            username: options.username,
            password: options.password,
          },
        }),
      ],
      module: CustomerInvoicesModule,
      providers: createCustomerInvoicesProviders(options),
    };
  }

  /**
   * Registers a configured customer-invoices Module for import into the current module
   * using dynamic options (factory, etc)
   */
  public static registerAsync(
    options: CustomerInvoicesAsyncOptions,
  ): DynamicModule {
    return {
      module: CustomerInvoicesModule,
      imports: options.imports || [],
      providers: [...this.createProviders(options)],
    };
  }

  private static createProviders(
    options: CustomerInvoicesAsyncOptions,
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createOptionsProvider(options)];
    }

    return [
      this.createOptionsProvider(options),
      {
        provide: options.useClass,
        useClass: options.useClass,
      },
    ];
  }

  private static createOptionsProvider(
    options: CustomerInvoicesAsyncOptions,
  ): Provider {
    if (options.useFactory) {
      return {
        provide: CUSTOMER_INVOICES_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      };
    }

    // For useExisting...
    return {
      provide: CUSTOMER_INVOICES_OPTIONS,
      useFactory: async (optionsFactory: CustomerInvoicesOptionsFactory) =>
        await optionsFactory.createFtNestCustomerInvoicesOptions(),
      inject: [options.useExisting || options.useClass],
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

Jay*_*iel 7

好了,扣在,导致没有一个简单的答案,但有一种围绕它的方式。

首先,没有办法从另一个模块的异步注册方法调用一个模块的异步注册方法。至少不使用传入的异步配置,因此我将向您展示可以做什么。

选项一。 imports

可能是仍然适用于异步注册方法的三个选项中最简单的一个。imports传递给异步配置的数组在模块中完全可用,因此您最终可以执行以下操作

CustomerInvoicesModule.registerAsync({
  imports: [
    HttpModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: httpConfigFactory,
    }),
    ConfigModule
  ],
  inject: [ConfigService],
  useFacotry: customerInvoicesConfigFactory,
})
Run Code Online (Sandbox Code Playgroud)

这将公开HttpService其完整配置。唯一需要注意/小心的是确保您跟踪注册级别。

选项二。 asyncConfigInterface

这是一个有趣的。您可以将异步配置选项更改为类似于

export interface CustomInvoiceModuleAsyncOptions {
  http: HttpModuleAsyncOptions;
  useClass?: RegularValueHere;
  useFacotry?: RegularValueHere;
  useValue?: RegularValueHere;
  inject: injectionArray;
  imports: importsArray;
}
Run Code Online (Sandbox Code Playgroud)

现在在你的registerAsync方法中你可以做

static registerASync(options: CustomerInvoiceModuleAsyncOptions): DynamicModule {
  return {
    module: CustomerInvoicesModule,
    imports: [HttpModule.registerAsync(options.http)]
    providers: [...this.createProvider(options)],
    exports: [...this.createProvider(options)],
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,这意味着配置被传递到HttpModule您模块的选项内部,这看起来很难看,但它会为正确的模块提供选项。

选项三。 register/forRoot

只是不要直接使用ConfgModule和使用registerforRoot方法。通过这种方式,您可以快速轻松地传递配置值。如果您不介意不使用ConfigModule.


小智 5

Expanding on Jay's answer, there may be an Option Four that works for you.

Option Four. extraProviders

Not all modules support this (but HttpModule seems to - so you're in luck).

public static registerAsync(options: CustomerInvoicesAsyncOptions,): DynamicModule {
    const providers = this.createProviders(options);
    return {
      module: CustomerInvoicesModule,
      imports: [
          HttpModule.registerAsync({
              imports: options.imports || [],
              // Factory is passed the injected CustomerInvoicesOptions as argument
              // which is based on the result of the extra providers
              useFactory: async (options: CustomerInvoicesOptions) => ({
                  url: options.url,
                  auth: {
                      username: options.username,
                      password: options.password,
                  },
              }),
              inject: [CUSTOMER_INVOICES_OPTIONS],
              extraProviders: providers,
          }),
          ...(options.imports || []),
        ],
        providers: [...providers],
    };
}
Run Code Online (Sandbox Code Playgroud)

Jay may be able to clarify if this approach is correct, but it seems to work for me.

Fallback. @Global()/export

Instances where the module does not expose an extraProviders property in their AsyncOptions you may be able to circumvent this by making the module @Global() and exporting the CUSTOMER_INVOICES_OPTIONS provider.

e.g for the PassportModule (which unfortunately does not expose an extraProviders property)

@Global()
@Module({...})
export class CustomerInvoicesModule {
    ....
    public static registerAsync(options: CustomerInvoicesAsyncOptions,): DynamicModule {
        return {
            module: CustomerInvoicesModule,
            imports: [
                PassportModule.registerAsync({
                    useFactory: async (options: CustomerInvoicesOptions) => ({
                        defaultStrategy: options.oauthEnabled ? 'jwt' : 'no-auth',
                    }),
                    inject: [CUSTOMER_INVOICES_OPTIONS],
                }),
                ...(options.imports || []),
            ],
            providers: [...this.createProviders(options)],
            exports: [CUSTOMER_INVOICES_OPTIONS],
        };
    }
    ....
Run Code Online (Sandbox Code Playgroud)

This @Global() approach also works, but isn't particularly nice.

Other things I have tried, which fail:

  • Circular import of the outer dynamic module into the inner, with the options provider key injected - the injected provider key cannot be resolved.
  • Same circular import, but with a forwardRef(() => MyModule) - this always fails with a metatype is not a constructor error.

Hope this insight helps, and any NestJS experts can correct issues with either approach.