Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Injectables with nestjs-modules #89

Closed
jmcdo29 opened this issue Dec 28, 2019 · 2 comments
Closed

Multiple Injectables with nestjs-modules #89

jmcdo29 opened this issue Dec 28, 2019 · 2 comments

Comments

@jmcdo29
Copy link
Contributor

jmcdo29 commented Dec 28, 2019

It would be awesome if there was a way to provide multiple injectable values from a single module. I'm thinking something like

createConfigurableDynamicRootModule<T, U>(moduleConfigToken: [InjectionTokens] | InjectionToken, moduleProperties: Partial<
    Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'>
  > = {
    imports: [],
    exports: [],
    providers: []
  }
)

Where you could provide multiple tokens. Maybe create a third parameter for a token map that tells the system how to create the relationship between the tokens and their configs. Say if multiple tokens were provided like SERVICE_OPTIONS and INTERCEPTOR_OPTIONS then there could be an overall options options passed to forRoot or returned from forRootAsync that could look like

{
  service: myServiceOptions,
  interceptor: myInterceptorOptions,
}

and a map (object) that looks like

{
  SERVICE_OPTIONS: service,
  INTERCEPTOR_OPTIONS: interceptor
}

(or maybe reversed) so that when in a service we have @Inject(SERVICE_OPTIONS) we get the value myServiceOptions. What do you think? Let me know if you need more insight.

@WonderPanda
Copy link
Collaborator

Looking further into this I feel like if your goal is to have multiple injectables provided from your dynamic module that the current APIs support that relatively easily. The boilerplate that this library removes is for the configuration of your Dynamic Module, not for what it will provide. So in that sense you can pass as complex an object as you want under one Injection token for the configuration.

Check out this snippet from the RabbitMQ library:

export class RabbitMQModule
  extends createConfigurableDynamicRootModule<RabbitMQModule, RabbitMQConfig>(
    RABBIT_CONFIG_TOKEN,
    {
      providers: [
        {
          provide: AmqpConnection,
          useFactory: async (
            config: RabbitMQConfig
          ): Promise<AmqpConnection> => {
            const connection = new AmqpConnection(config);
            await connection.init();
            const logger = new Logger(RabbitMQModule.name);
            logger.log('Successfully connected to RabbitMQ');
            return connection;
          },
          inject: [RABBIT_CONFIG_TOKEN]
        }
      ],
      exports: [AmqpConnection]
    }
  )

You can see that it has one provider which is implemented with a factory function which relies on the Config object (in this case RabbitMQConfig) to build up and provide an AmqpConnection.

If the config object for this dynamic module were more complex (with multiple top level object keys that each were responsible for configuring an individual provider), I could easily add more providers to the array in the above snippet and have each of them return something else based on the Config object:

export class RabbitMQModule
  extends createConfigurableDynamicRootModule<RabbitMQModule, RabbitMQConfig>(
    RABBIT_CONFIG_TOKEN,
    {
      providers: [
        {
          provide: AmqpConnection,
          useFactory: async (
            config: RabbitMQConfig
          ): Promise<AmqpConnection> => {
            const connection = new AmqpConnection(config);
            await connection.init();
            const logger = new Logger(RabbitMQModule.name);
            logger.log('Successfully connected to RabbitMQ');
            return connection;
          },
          inject: [RABBIT_CONFIG_TOKEN]
        },
        {
          provide: AmqpConnection,
          useFactory: async (
            config: RabbitMQConfig
          ): Promise<SomeOtherThing> => {
            // do something with config
           return new SomeOtherThing();
          },
          inject: [RABBIT_CONFIG_TOKEN]
        }
      ],
      exports: [AmqpConnection]
    }
  )

I'm not sure I see the use case for needing to inject the configuration options to other places after the fact as generally it's simply used to bootstrap the dynamic module properly but you could easily inject the config object and then just access the property on it that you need eg options.service in your example or you could even use the snippets I've provided as a starting point to provide them dynamically using providers as a means of destructuring the top level config object into discrete parts each using it's own new Injectable token.

Let me know if this allows you to accomplish the scenario that you're after

@WonderPanda
Copy link
Collaborator

I made an example app so you can see it in action. You can pull it down and run it but the relevant files are here:

https://github.com/WonderPanda/dynamic-modules-example/blob/master/src/dynamic-example/dynamic-example.module.ts

https://github.com/WonderPanda/dynamic-modules-example/blob/master/src/app.module.ts

https://github.com/WonderPanda/dynamic-modules-example/blob/master/src/app.controller.ts

Feel free to re-open this if you feel this doesn't meet your needs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants