Create array of providers from Dynamic Module options

342 views Asked by At

I am working with NestJS and I am building re-usable modules, configurable via forRoot and forRootAsync static methods.

I am looking for a way to provide multiple providers of the same class, based on the module options.

export type MyModuleOptions = {
  services: MyServiceOptions[];
}

export type MyServiceOptions = {
  name: string;
  url: string;
}

Based on this options, the result is easy to achieve in a basic forRoot method:

export class MyModule {
  static forRoot(options: MyModuleOptions): DynamicModule {
    const providers = options.services.map((service_options) => {
//                    \__________________/
//                  loop over the options to generate each provider
      return {
        provide: 'SERVICE_' + service_options.name,
//               \_______________________________/
//                generate the provider token based on the options
        useFactory: () => {
          return new MyService(service_options.url);
        }
      }
    });

    return {
      module: MyModule,
      providers: [...providers]
    }
  }
}

Now I can use it in my AppModule ...

@Module({
  imports: [
    MyModule.forRoot({
      services: [
        { name: 'SERVICE_1', url: 'https://...' },
        { name: 'SERVICE_2', url: 'https://...' } 
      ]
    })
  ]
})
export class AppModule {}

... and inject the specific service I need:

export class TestService {
  constructor(@Inject('SERVICE_SERVICE_1') private service: MyService) {}
//            \_________________________/
//           Dynamically generated by MyModule
}

The issue

Now I want to implement something similar but using a forRootAsync method, so instead of providing hard-coded urls for my services I can fetch them from environment variable with the config service.

The desired usage would look something like this:

@Module({
  imports: [
    MyModule.forRootAsync({
      useFactory: (config: ConfigService) => {
        return {
          services: [
            { name: 'service_1', url: config.get('url_1') },
            { name: 'service_2', url: config.get('url_2') },
//                                    \_________________/
//                    use external dependency to configure the module, config or something else
          ]
        }
      },
      inject: [ConfigService]
    })
  ]
})
export class AppModule {}

So I have created the async options for the module:

export type MyModuleAsyncOptions = {
  useFactory: (...args: any[]) => MyModuleOptions;
  inject: InjectionToken[];
}

When looking to other modules implementation, the common practice seems to create a provider for the module options in the forRootAsync method:

export class MyModule {
  forRootAsync(options: MyModuleAsyncOptions) {
    return {
      module: MyModule,
      providers: [
        {
          provide: 'MY_MODULE_OPTIONS',
          useFactory: (...args: any[]): MyModuleOptions => {
            return options.useFactory(...args);
          },
          inject: [...options.inject]
        }
      ]
    }
  }
}

Now that I have my module options, how can I build multiple providers with it ?

It seems like the only thing I can do is to inject the options in a single provider, I could not find a way to loop over the options to generate the desired providers.

Thank you in advance for any help on the topic !

0

There are 0 answers