5

In my main module (A), I import an external module (B) with its configuration:

imports: [
NestAuthModule.registerAsync({
  inject: [authConfig.KEY],
  useFactory: async (config: ConfigType<typeof authConfig>) => ({
    google: config.google,
    jwt: config.jwt,
  }),
})
]
export class NestAuthModule {
  static registerAsync(options: AuthModuleAsyncOptions): DynamicModule {
    return this.createModule(
      this.createAsyncProviders(options),
      options.imports || []
    );
  }

  private static createModule(
    providers: Provider[],
    imports: Array<
      Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
    >
  ) {
    return {
      module: NestAuthModule,
      controllers: [AuthController],
      providers: [
        ...providers
      ],
      imports: [
        ...imports,
        JwtModule.registerAsync({
          inject: [AuthModuleOptions],
          useFactory: async (options: AuthModuleOptions) => {
            return {
              secret: options.jwt.secretKey,
              signOptions: {
                expiresIn: options.jwt.expires,
              },
            };
          },
        }),
      ],
      exports: [AuthModuleOptions],
    };
  }

  private static createAsyncProviders(
    options: AuthModuleAsyncOptions
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createAsyncOptionsProvider(options)];
    } else if (options.useClass) {
      return [
        this.createAsyncOptionsProvider(options),
        {
          provide: options.useClass,
          useClass: options.useClass,
        },
      ];
    }
    throw new Error('missing provider config');
  }

  private static createAsyncOptionsProvider(
    options: AuthModuleAsyncOptions
  ): Provider {
    const useFactory =
      options.useFactory ??
      (async (optionsFactory: AuthOptionsFactory) =>
        await optionsFactory.createAuthOptions());
    const inject = options.useFactory
      ? options.inject || []
      : [options.useExisting || options.useClass];
    return {
      provide: AuthModuleOptions,
      useFactory,
      inject,
    };
  }
}

Notice that in module B, I am importing a third party module using a factory and it needs module B configuration. However, it is not yet available because it is async. It is going to be provided when module is finally instanced. However, I am already injecting this provider in my imports. So, I get an injection error:

[ExceptionHandler] Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument AuthModuleOptions at index [0] is available in the JwtModule context.
Potential solutions:
- If AuthModuleOptions is a provider, is it part of the current JwtModule?

I am pretty sure there is a solution, but I haven't found it yet.

Arnaud
  • 71
  • 5
  • I've got a [pretty detailed answer to a similar question here](https://stackoverflow.com/questions/63356440/how-to-import-a-registerasync-in-a-dynamic-nestjs-module/66147515#66147515) – Jay McDoniel Jul 14 '22 at 15:46

1 Answers1

1

I just stumbled on your same problem and found a solution. You have to provide your options provider to the sub module (in this case JwtModule):

export const importAsyncModule = (
  parentModule: DynamicModule,
  subModule: DynamicModule,
  optionsToken: InjectionToken,
) => {
  const provider = parentModule.providers?.find(
    (x) => (x as any)?.provide === optionsToken,
  );
  if (!provider)
    throw new Error(`Provider for ${optionsToken.toString()} not found`);
  subModule.providers = subModule.providers ?? [];
  subModule.providers.unshift(provider);
  parentModule.imports = parentModule.imports ?? [];
  parentModule.imports.push(subModule);
  return subModule;
};

If m is your parent module, you would do:

const jwtModule = JwtModule.registerAsync({
  useFactory: (opt: AuthenticationModuleOptions) => opt.jwt,
  inject: [MODULE_OPTIONS_TOKEN],
});
importAsyncModule(m, jwtModule, MODULE_OPTIONS_TOKEN);
random42
  • 75
  • 7