Nest: import module in condition based

Created on 25 Apr 2018  路  10Comments  路  Source: nestjs/nest

Is there a way to import module or add component to main module on condition based

ex:


@Module(
    {
        modules: [ModuleA, ModuleB, ModuleC]
    })

In this in some place i need only ModuleA, not ModuleB and ModuleC, but in some condition, I will be using ModuleB and not others.

Is there a way to import here based on some condition..

Also, I have the same situation in component loading. is that possible in components too?
I would be happy if I have any one of these


Nest version: 4.6.4


For Tooling issues:
- Node version: 8.9.4  
- Platform: Windows  



Most helpful comment

Okay, i know someone can help with that :)

cc @bashleigh

All 10 comments

I think you are looking for Dynamic Modules
see here https://docs.nestjs.com/modules
at the end of this page.

in Dynamic Module you should have control of what will be included in components array, and so imports.

@shekohex +1
Exactly. The dynamic modules feature is what are you looking for 馃檪 Let us know if you face any issues.

Dynamic modules are great! I'm wondering if there's a way to use DI with Dynamic Modules? I want my imports to be based on a ConfigService value.

@shekohex Thanks a lot for the response! I've posted on Discord and Gitter and haven't gotten any responses. I've had a look at the code and from what I can see, it creates an instance of the service dependent on the options passed. I can't figure out how I can apply it to what I want I want to do, maybe some code can help clarify the issue

@Module({
  imports: [EnvModule],
  providers: [EnvService],
})
export class ApiModule {
  static init(env: EnvService): DynamicModule {
    const isProduction = env.isProduction;

    const imports = [DatabaseModule, CompanyModule, StoreModule, UserModule, UserCompanyInfoModule];
    if (!isProduction) {
      imports.push(SeederModule);
    }

    return {
      module: ApiModule,
      imports,
    };
  }
}

I'm trying to inject the service and use one of it's methods to determine the value of the imports array of the module. Any ideas?

Okay, i know someone can help with that :)

cc @bashleigh

I would suggest making a dynamic module and making dynamic providers. You could also use the ClassProvider to keep the token the same with @Inject('CUSTOM_TOKEN').

@Module({})
export class TestClass {
  public static forRoot (options: { production: boolean } ): DynamicProvider {

    const providers: Provider[] = [];

     providers.push(options.production ? ProviderA : ProviderB);

     // Second method with useClass Provider
     // This would mean your token for the provider will be the same regardless of class used
     providers.push({
       provide: 'CUSTOM_TOKEN',
       useClass: options.production ? ProviderA : ProviderB,
     });

    return {
      module: TestClass,
      providers,
    };
  }
}

I'm trying to inject the service and use one of it's methods to determine the value of the imports array of the module. Any ideas?

From what you're saying here you want to inject the service EnvService. This means having a provider that depends on that provider. For example

@Module({})
export class TestClass {
  public static forRoot (options: {
    useFactory: (...args: any[]) => { production: boolean },
    injects: Array<Type<any> | string | any>
  }): DynamicProvider {

    const providers: Provider[] = [
      {
        provide: 'TEST_CLASS_PRODUCTION_BOOLEAN',
        useFactory: options.useFactory,
        injects: options.injects,
      },
      {
        provide: 'CUSTOM_PROVIDER_TOKEN',
        useFactory: (options: {production: boolean}) => production ? ProviderA : ProviderB,
        injects: ['TEST_CLASS_PRODUCTION_BOOLEAN'],
      },
    ];

    return {
      module: TestClass,
      providers,
    };
  }
}
@Module({
  imports: [
    TestClass.forRoot({
      useFactory: (envService: EnvService) => ({ production: envService.isProduction }),
      injects: [EnvService],
    }),
  ],
  providers: [EnvService]
})
export class SomeOtherModule {}

Hopefully! In the above example you now have this sort of setup and classes will be instanced in this order:
EnvService => 'TEST_CLASS_PRODUCTION_BOOLEAN' => 'CUSTOM_PROVIDER_TOKEN'.

I've not tested it but I think this is the sort of method you was looking for?

Edit

You could cut out the other provider if wanted like so, I was trying to avoid a hard coded dependancy but I doubt it matters in your case?

 {
    provide: 'CUSTOM_PROVIDER_TOKEN',
    useFactory: (envService: EnvService) =>envService.isProduction ? ProviderA : ProviderB,
    injects: [EnvService],
 },

Hi @bashleigh Thanks so much for the response and taking the time to write out these examples, I genuinely appreciate it! I'll try implementing this and I'll be back with feedback. Also, I don't know if it matters but there's a slight correction:

From what you're saying here you want to inject the service EnvService. This means having a provider that depends on that provider. For example

What I actually want is a DynamicModule that determines it's imports: [] based on a value of the EnvService. So it's technically a "module that depends on a provider"... I think.

Hi @bashleigh & @shekohex I wasn't able to tailor your answer to my problem, thank you for the time still. I was however able to track down a similar request here on Github. They're quite a few actually one such issue. The solution there is "just don't do it or do it manually". I ended up creating an instance of my EnvService and luckily it doesn't depend on anything else so it was a breeze.

@Module({
  imports: [CompanyModule, StoreModule, UserModule, UserCompanyInfoModule],
})
export class ApiModule {
  static async init(): Promise<DynamicModule> {

    const env = new EnvService();

    const seederModuleArray = [];
    if (!env.isProduction) {
      seederModuleArray.push(SeederModule);
    }

    return {
      module: ApiModule,
      imports: seederModuleArray,
    };
  }
}

That works fine. I tried using the application context:

const app = await NestFactory.createApplicationContext(AppModule);
const env = app.get(EnvService);

But this obviously resulted in a circular dependency. Using forwardRef resulted in an infinite loop of each module trying to instantiate the other.

The thing I don't understand though is this part in the documentation:

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private readonly catsService: CatsService) {}
}

What configuration can you actually do in Modules?? You can Inject the service yeah, but then what? Can you affect the metadata of the Module in the constructor function??

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FranciZ picture FranciZ  路  3Comments

marshall007 picture marshall007  路  3Comments

hackboy picture hackboy  路  3Comments

menme95 picture menme95  路  3Comments

artaommahe picture artaommahe  路  3Comments