#useExisting vs useClass configuration for a third-party module

21 messages · Page 1 of 1 (latest)

unreal geode
#

I am using MongooseModule and my custom AppConfigService that implements MongooseOptionsFactory interface.
so I am able to use just class name in configuration of mongoose:

@Module({
  imports: [
    AppConfigModule,
    MongooseModule.forRootAsync({
      imports: [AppConfigModule],
      useExisting: AppConfigService,
      inject: [AppConfigService],
    }),
    AuthModule,
  ],
  controllers: [AppController],
})
export class AppModule {}

I do not understand why I must specify imports and inject properties here. AppConfigModule exports AppConfigService.

I do not know if it makes any difference, but I am using Fastify.

cursive fog
#

inject is not necessary. It's only needed with useFactory to mimic typescript's metadatas emissions
useClass is to use a class based provider vs a direct value or a factory
useExisting is essentially to create an alias for an existing provider
imports is necessary because the dynamic configuration resolver is essentially a module in and of itself

unreal geode
#

@cursive fog thank you!

I have read docs about these ways, but I am still a little bit confused.

  1. imports is necessary because this configuration is not part of DI? I mean MongooseModule cannot go through hierarchy and find right dependency.
  2. what is the difference between useClass and useExisting in such cases? There is only object literal example for creating alias with useExisting, but I don't understand behaviour in cases like this one. Also useClass creates a new instance of class every time?
#

with useClass it doesn't work:

@Module({
  imports: [
    AppConfigModule,
    MongooseModule.forRootAsync({
      imports: [AppConfigModule],
      useClass: AppConfigService,
    }),
    AuthModule,
  ],
  controllers: [AppController],
})
export class AppModule {}
cursive fog
#

imports is necessary because of how Nest's DI resolution works. It doesn't travel to "parent" modules to find dependencies, it only travels from the current module to the exports of imported modules and global modules. So the forRootAsync needs to either provide the provider, or it needs to import a module that does

useClass creates a new instance of the AppConfigService in the context of the MongooseModule.forRootAsync() module

useExisting uses an existing instance of the AppConfigService in the context of theMongooseModule.forRootAsync().

Your setup fails because The AppConfigModule doesn't export the ConfigModule that provides the expected ConfigService so that AppConfgService can be created.

What you probably really want to do, instead, is have a MongoConfigService that injects AppConfigService and return the expected configuration from a createMongooseOptions method

unreal geode
#

so I will have separated classes for each module (for example, I am using JwtModule in the same way)?
why this approach better?

#

in this case I do not need AppConfigService .
this is how it is now:

@Injectable()
export class AppConfigService
  implements MongooseOptionsFactory, JwtOptionsFactory
{
  constructor(private readonly envService: ConfigService) {}

  createMongooseOptions(): MongooseModuleOptions {
    const uri = this.envService.getOrThrow<string>('MONGODB_CONNECTION');
    return { uri };
  }
// ...
}
cursive fog
#

That's an option! Another is to make your AppConfigService implement all the methods and interfaces necessary, and then just use useExisting for them all

unreal geode
#

I got it!
Thank you so much.

unreal geode
#

@cursive fog I have one more question here:
why useClass fails here but useExisting works?

I've just re-read what you write here.

so if DI goes only within current module and exports section of imported ones, and as far as I understand Mongoose has kinda 'isolated' context, it's like we creating a new module . That's why we need to import AppConfigModule here.

in case of useExisting:

  1. looking in imported in Mongoose AppConfigModule exports section.
  2. Found AppConfigService.
  3. Which instance of AppConfigService is used? The same that I am using in my other service?

in case of useClass:
0. Creating a new instance of AppConfigService.

  1. AppConfigService has dependecy of ConfigService.
  2. Looking in imported modules. Not found.
  3. Throws an error.
cursive fog
cursive fog
#

Realized I never answered this:

Which instance of AppConfigService is used? The same that I am using in my other service?
It's the same one that comes from AppConfigModule

unreal geode
#

Let's assume I have 2 regular modules: module A, module B.
I improted AppConfigModule in module A and in module B. AppConfigModule will create own instance for each module or it only creates providers once?

also, hm.. I am wondering why useClass didn't work:

@Module({
  imports: [ConfigModule.forRoot()],
  providers: [AppConfigService],
  exports: [AppConfigService],
})
export class AppConfigModule {}

ConfigModule is not global, but imported directly into module. So I guess we should be fine?

#

Could you suggest any resources that I can read about DI and this all stuff in details? Some basics only covered in the docs.

cursive fog
cursive fog
# unreal geode Could you suggest any resources that I can read about DI and this all stuff in d...

Unfortunately, most of the knowledge I have around the DI process comes from being involved with the framework for the past 5 years. Tinkering, self learning, helping around this server, being in the GitHub issues. Nothing is really written down in depth in paper (or Web interface) because it's hard to get it to come across clearly and because once you know, it's hard to write at it in a simple approach. Might be another candidate for an advanced page I eventually write though

unreal geode
#

got it.
and what about that case with 'useClass'?
why did it not work even though ConfigModule is the dependency of its Module?

cursive fog
#

Because the ApiConfigModule only imports the ConfigModule it doesn't re-export it.

Re-exporting is a way to pass on the exports of modules that are already imported

#

So, say that AMod imports BMod which imports and exports CMod and has BServ in providers and exports. CMod also has CServ in providers and exports. AMod will have access to BServ and CServ for to BMod exporting CMod and BServ and CMod exporting CServ.

Does that make sense for module re-exporting?

unreal geode
#

hm... I think I got it now (hopefully)
thank you so much.
we definitely need resources on this.