DeX3
DeX3

Reputation: 5569

nestjs: dynamic global module

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

Upvotes: 9

Views: 9549

Answers (3)

I don't claim to know why, but the @Global decorator apparently doesn't work for registering a dynamic module as global. The NestJS documentation specifies that a dynamic module can be registered as global by adding a global field with value true to the object returned by the static method handling the dynamic configuration (forRoot in your case):

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

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

If I find the motivation to figure out why the decorator isn't useful here, I'll edit this response with an explanation.

Upvotes: 0

Ing.LkRuiZ
Ing.LkRuiZ

Reputation: 282

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).

Upvotes: 7

Jesse Carter
Jesse Carter

Reputation: 21207

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

Upvotes: 0

Related Questions