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

时间:2020-08-11 10:37:46

标签: typescript dynamic import module nestjs

使用Advanced NestJS研究动态模块:如何构建完全动态的NestJS模块。https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370

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

但是我的问题是,如果我使用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],
    };
  }
}

2 个答案:

答案 0 :(得分:5)

好吧,别紧张,因为没有简单的答案,但是有解决方法。

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

选项一。 imports

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

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

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

选项二。 asyncConfigInterface

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

export interface CustomInvoiceModuleAsyncOptions {
  http: HttpModuleAsyncOptions;
  useClass?: RegularValueHere;
  useFacotry?: RegularValueHere;
  useValue?: RegularValueHere;
  inject: injectionArray;
  imports: importsArray;
}

现在在您的 registerAsync 方法中您可以执行

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

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

选项三。 register/forRoot

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

答案 1 :(得分:5)

扩展 Jay 的回答,可能有适合您的选项四。

选项四。 extraProviders

并非所有模块都支持此功能(但 HttpModule 似乎支持 - 所以您很幸运)。

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],
    };
}

Jay 或许能够澄清这种方法是否正确,但它似乎对我有用。

后备。 @Global()/export

如果模块未在其 extraProviders 中公开 AsyncOptions 属性,您可以通过使模块 @Global() 并导出 CUSTOMER_INVOICES_OPTIONS 提供程序来规避此问题。

例如对于 PassportModule(不幸的是它没有公开 extraProviders 属性)

@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],
        };
    }
    ....

这种 @Global() 方法也有效,但不是特别好。

我尝试过的其他失败的事情:

  • 将外部动态模块循环导入内部,并注入选项提供程序密钥 - 无法解析注入的提供程序密钥。
  • 相同的循环导入,但带有 forwardRef(() => MyModule) - 这总是失败并出现 metatype is not a constructor 错误。

希望此见解有所帮助,任何 NestJS 专家都可以使用任一方法纠正问题。