Pesquisa de site

typescript - Como importar um registerAsync em um módulo dinâmico do Nestjs?


tem analisado os módulos dinâmicos usando o Advanced NestJS: Como construir módulos NestJS completamente dinâmicos.

Pelo que eu vi, a maioria das pessoas usa este guia para construir um módulo dinâmico síncrono/assíncrono.

Mas minha pergunta é, que se eu usar o método registerAsync, e meu módulo dinâmico precisar importar o HttpModule, e as opções de registro do HttpModule forem fornecidas pelo meu módulo dinâmico.

Como você importa um módulo dentro de um módulo dinâmico, onde as opções são fornecidas pelo módulo dinâmico? Ou isso é a maneira errada de lidar com esse problema? Se for, como você estruturaria isso?


Aqui está o código. Que é praticamente uma cópia exata do tutorial. Como você pode ver no método register, é simples - eu apenas passo as opções. Já no registerAsync, estou tendo dificuldade em descobrir o que fazer.


Qualquer ajuda é muito apreciada :)

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

Resposta #1

Ok, aperte o cinto, porque não há uma resposta fácil, mas existem meio que maneiras de contornar isso.

Primeiramente, não há como chamar o método de registro assíncrono de um módulo a partir do método de registro assíncrono de outro módulo. Pelo menos não usando a configuração assíncrona que foi passada, então, em vez disso, vou mostrar o que pode ser feito.

Opção Um. imports

Provavelmente a mais fácil das três opções que ainda funciona com o método de registro assíncrono. O array imports que é passado para a configuração assíncrona está completamente disponível no módulo, então você pode acabar fazendo algo como

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

Isso vai expor o HttpService em sua configuração completa. A única coisa com que você deve ter cuidado é garantir que está acompanhando os níveis de registro.

Opção Dois. asyncConfigInterface

Esta é a divertida. Você pode alterar suas opções de configuração assíncronas para algo mais ou menos assim

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

E agora, no seu método registerAsync, você pode fazer

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

Agora, isso significa que a configuração é passada para o HttpModule dentro das opções do seu módulo, o que meio que fica feio, mas faz com que as opções cheguem ao módulo certo.

Opção Três. register/forRoot

Apenas não use o ConfgModule e use os métodos register ou forRoot diretamente. Dessa forma, você pode passar os valores de configuração rapidamente e facilmente. Muito direto se você não se importar em não usar o ConfigModule.

Resposta #2

Expandindo a resposta de Jay, pode haver uma Opção Quatro que funcione para você.

Opção Quatro. extraProviders

Nem todos os módulos suportam isso (mas HttpModule parece suportar - então você teve sorte).

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 pode ser capaz de esclarecer se essa abordagem está correta, mas parece funcionar para mim.

Fallback. @Global()/export

Em casos onde o módulo não expõe uma propriedade extraProviders em seu AsyncOptions, você pode ser capaz de contornar isso tornando o módulo @Global() e exportando o provedor CUSTOMER_INVOICES_OPTIONS.

por exemplo, para o PassportModule (que infelizmente não expõe uma propriedade 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],
        };
    }
    ....

Esta abordagem @Global() também funciona, mas não é particularmente agradável.

Outras coisas que eu tentei, que falharam:

  • Importação circular do módulo dinâmico externo para o interno, com a chave do provedor de opções injetada - a chave do provedor injetado não pode ser resolvida.
  • Mesmo import circular, mas com um forwardRef(() => MyModule) - isso sempre falha com um erro metatype is not a constructor.

Espero que esse insight ajude, e quaisquer especialistas em NestJS podem corrigir problemas com qualquer uma das abordagens.

Artigos relacionados