typescript - Hur importerar man en registerAsync i en dynamisk Nestjs-modul?


har tittat på de dynamiska modulerna med Advanced NestJS: Hur man bygger helt dynamiska NestJS-moduler.

Från vad jag har sett använder de flesta människor denna guide för att bygga en synkron/asynkron dynamisk modul.

Men min fråga är, om jag använder registerAsync-metoden, och min dynamiska modul behöver importera HttpModule, och HttpModules register-alternativ tillhandahålls av min dynamiska modul.

Hur importerar man ett modul inom ett dynamiskt modul, där alternativen tillhandahålls av det dynamiska modul? Eller är det fel sätt att hantera detta problem? Om så är fallet, hur skulle du strukturera det?


Här är koden. Vilket är i princip en exakt kopia av handledningen. Som du kan se i registermetoden, är det enkelt - jag skickar bara in alternativen. registerAsync däremot, har jag problem med att lista ut vad jag ska göra.


All hjälp uppskattas mycket :)

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

Svar #1

Okej, spänn fast dig, för det finns inget enkelt svar, men det finns typ sätt att komma runt det.

För det första finns det inget sätt att anropa en moduls asynkrona registreringsmetod från en annan moduls asynkrona registreringsmetod. Åtminstone inte med den asynkrona konfiguration som skickades in, så istället kommer jag att visa dig vad som kan göras.

Alternativ Ett. imports

Förmodligen det enklaste av de tre alternativen som fortfarande fungerar med den asynkrona registreringsmetoden. imports-arrayen som skickas till den asynkrona konfigurationen är helt tillgänglig i modulen, så du kan till slut göra något som

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

Detta kommer att exponera HttpService i dess fulla konfiguration. Det enda att vara medveten om/viktig med är att se till att du håller koll på registreringsnivåerna.

Alternativ två. asyncConfigInterface

Det här är den roliga. Du kan ändra dina asynkrona konfigurationsalternativ till att vara något i stil med

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

Och nu i din registerAsync-metod kan du göra

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

Nu betyder detta att konfigurationen skickas till HttpModule inuti alternativen för din modul, vilket ser lite fult ut, men det får alternativen till rätt modul.

Alternativ tre. register/forRoot

Använd bara inte ConfgModule och använd register eller forRoot-metoderna direkt. På detta sätt kan du skicka konfigurationsvärden snabbt och enkelt. Väldigt enkelt om du inte har något emot att inte använda ConfigModule.

Svar #2

Utvidgande av Jays svar, det kan finnas ett alternativ fyra som fungerar för dig.

Alternativ Fyra. extraProviders

Inte alla moduler stöder detta (men HttpModule verkar göra det - så du har tur).

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 kanske kan klargöra om detta tillvägagångssätt är korrekt, men det verkar fungera för mig.

Återfall. @Global()/export

I fall där modulens AsyncOptions inte exponerar en extraProviders-egenskap kan du kanske kringgå detta genom att göra modulen @Global() och exportera CUSTOMER_INVOICES_OPTIONS-providern.

t.ex. för PassportModule (som tyvärr inte exponerar en extraProviders-egenskap)

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

Detta @Global()-tillvägagångssätt fungerar också, men är inte särskilt trevligt.

Andra saker jag har provat, som misslyckas:

  • Cirkulär import av den yttre dynamiska modulen in i den inre, med alternativleverantörens nyckel injicerad - den injicerade leverantörsnyckeln kan inte lösas.
  • Samma cirkulära import, men med en forwardRef(() => MyModule) - detta misslyckas alltid med ett metatype is not a constructor-fel.

Hoppas att denna insikt hjälper, och att eventuella NestJS-experter kan rätta till problem med båda tillvägagångssätten.

Relaterade artiklar