5
votes

The official nest docs on modules explain about global modules and dynamic modules. I'm wondering if it is possible to combine the two patterns?

My use case is the following: I have a dynamic config module:

export class ConfigModule {
    static forRoot(baseConfigPath: string): DynamicModule {
        const providers = [{ provide: 'Config', useValue: configFactory(baseConfigPath) }];

        return {
            module: ConfigModule,
            providers,
            exports: providers,
        };
    }
}

This enables the config-module to be dependent on the passed in base config path. I can then import the module in the main app module as follows:

@Module({
    imports: [ConfigModule.forRoot(path.resolve(__dirname, '../config'))],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule implements NestModule {}

which is kind of nice. However, I do have lots of other modules (child modules of the app module, siblings to the config module), where I also want that same instance of the dynamic config module to be injectable. Would be great if I could mark the dynamic ConfigModule somehow as global - or is there another way?

I've already tried making the ConfigModule global with @Global, but that didn't work - here's a super minimal reduced example repo based on the nest starter created by nest new: https://github.com/DeX3/nest-di-playground

2
Have you tried making ConfigModule global with @Global? - Chau Tran
Yes, I did try that and I couldn't make it work. I've updated my question to include a minimal example. - DeX3

2 Answers

6
votes

Recently I built something like what you are describing, I got inspiration from nest/typeorm

@Module({})
export class LoggerModule {
  static forRoot(rootNamespace: string): DynamicModule {
    return {
      module: LoggerModule,
      imports: [LoggerCoreModule.forRoot(rootNamespace)],
    };
  }
}
@Global()
@Module({})
export class LoggerCoreModule {
  static forRoot(rootNamespace: string): DynamicModule {
    const namespaceProvider = {
      provide: LOGGER_ROOT_NAMESPACE,
      useValue: rootNamespace,
    };

    const loggerServiceProvider: Provider = {
      provide: LoggerService,
      useFactory: (namespace) => new LoggerService(namespace).init(),
      inject: [LOGGER_ROOT_NAMESPACE],
    };

    return {
      module: LoggerCoreModule,
      providers: [loggerServiceProvider, namespaceProvider],
      exports: [loggerServiceProvider, namespaceProvider],
    };
  }
}

Then, you will have a LoggerService in the global scope which was exported from LoggerCoreModule. You don't have to export the config you pass but I did it since my noncore module has a forFeature static method which needs it(I didn't paste the whole thing I built).

1
votes

Like @DeX3 has pointed out you can achieve this behavior using the @Global module decorator. This is mentioned in the NestJS docs in the section about modules:

https://docs.nestjs.com/modules

You can check out a more in depth/real world example of this in action by taking a look at some of the third party modules that have been built for the NestJS ecoystem like the TypeOrm package:

https://github.com/nestjs/typeorm/blob/master/lib/typeorm-core.module.ts