#Dynamic module options access

16 messages · Page 1 of 1 (latest)

lofty saffron
#

Hi! I've been following official documentation about Dynamic modules and I headed head first into a wall when it came to registerAsync.

The goal of my is to return a module where the @golevelup/nestjs-rabbitmq module is setup with all it's routes and services, provided that the rabbitmq is present in the application configuration.

Let's break it into pieces:

Here is what the MessagingModule looks like:

@Module({})
export class MessagingModule extends ConfigurableModule{
  static register(options: typeof OPTION_TYPE): DynamicModule{
      if(options.active) return null;
      return {module: MessagingCoreModule}
    }
  }

}

For all intense and purpose, this is how I defined the ConfigurableModule and the options:

export interface MessagingOptions{
  active: boolean
}

export interface MessagingModuleFactory{
  createMessagingOptions: () => MessagingOptions | Promise<MessagingOptions>
}


export const {ConfigurableModuleClass, OPTION_TYPE, ASYNC_OPTION_TYPE} = new ConfigurableOptionModuleBuilder<MessagingOptions>().setFactoryMethodName('createMessagingOptions').build()

Now, to my issue, the registerAsync function's options are as follows: imports, inject, useClass, useInstance, useFactory. I don't see a way to have access to the {active: boolean} property provided by one of the factories.

@Module({})
export class MessagingModule extends ConfigurableModule{
  static register(options: typeof OPTION_TYPE): DynamicModule{
      if(options.active) return null;
      return {module: MessagingCoreModule}
    }
  }

}

statis async registerAync(options: typeof ASYNC_OPTIONS_TYPE){
  ...
  // How do I access options.active here?
}

How should this function look like to have the same outcome as register using the provided options by the async factories?

#

I hope everything is explained clearly. Feel free to ask for other questions. I do have to address the fact that due to the nature of my work, I am limited to what code I can share, so hopefully, this is good enough.

clear pebble
#

If you're looking to dynamically add imprts or providers based on configuration, then I'm sorry to say that that is not possible with async configuration.

Those arrays must be synchronously known when the dynamic module defintion is returned.

#

What can be done though, is to register a fixed set of dummy providers and asynchronously change their imlementation based on the asynchronously resolved options.

lofty saffron
#

Well, then how do you access the uri of Typeorm forRootAsync function to establish the connection?

#

My use case requires that condition for the to be true in order to connect to the rmq broker.

#

The imports are declared in the MessagingCoreModule. No need to add any imports.

#

I just need to provide MessagingModule.registerAsync({...}) in order to return the MessagingModule if needed. I can do the dymmy modules in stead of null if that is the point you are making.

clear pebble
#

You can't dynamically import /or not) modules based on asynchronous configuration. The only ways is to conditionally import based on environment variable (or something that synchronously depends on an environmental variable)

lofty saffron
#

Ok. Then how would the dummy modules workaround look like? Maybe I am a little hard in the head and I will understand when I see it 😄

clear pebble
#

You can only use dummy providers, not dummy modules. But let me share an example with you

#

Here's an example of a dynamic ClsModule from my nestjs-cls package. You can asynchronously register it and select if you want a guard or an interceptor mounted. Now, Nest doesn't allow doing this asynchronously, the set of all providers must be known beforehand, so I always register both APP_GUARD and APP_INTERCEPTOR whose implementation is provided by a factory.

This factory injects the dynamically provided options and based on some condition, returns an actual implementation, or a dummy "no-op" version.

#

It's some advanced stuff, but sadly it doesn't work for whole modules.

I would reconsider if you actually need to dynamically inject configuration or if you can read it synchronously from environmental variables. The way Nest does configuration "officially" is unnecessarily complex.

lofty saffron
#

Ok. I kind of dig what you want me to do. Still, I don't see a way to use the options to determine what provider to use...

I ended up with this:

@Global()
@Module({})
export class MessagingService extends ConfigurableModuleClass{
  static register(options: typeof OPTIONS_TYPE): DynamicModule{
    return {
      module: MessagingModule,
      imports:[RannitMQModule.forRootAsync(RabbitMQModule, {
        imports: [ConfigService]
        useClass: AmqpConfig
        inject: [ConfigService]
       })],
      providers:[{provide: MessagingService, useValue: options.active ? MessagingService : MessagingDummyServce}],
      exports:[MessagingService]
    }
  }
}

But how would I do this in a registerAsync?

#

I would like to accept useClass, useFactory and other options too. Not just useFactory