#How to inject a different instance of a module every time it is imported?
54 messages · Page 1 of 1 (latest)
what about transient providers? https://docs.nestjs.com/fundamentals/injection-scopes#provider-scope
The service injects the connection from the module like so:
import { Injectable, Scope } from '@nestjs/common';
import { RedisDataSource } from './RedisDatasource';
@Injectable({
scope: Scope.TRANSIENT,
})
export class CacheService {
constructor(private dataSource: RedisDataSource) {}
async get(key: string) {
await this.dataSource.client.get(key);
}
async set(key: string, value: any) {
await this.dataSource.client.set(key, value);
}
}
Yes, I had tried that, since the RedisDataSource I inject is provided by the module, I guess it thus using the same instance.
It is provided in the module like so:
providers: [
{
provide: REDIS_CONNECTIONS_OPTIONS_TOKEN,
useFactory: moduleOptions.useFactory,
inject: moduleOptions.inject,
scope: Scope.TRANSIENT,
},
{
provide: RedisDataSource,
useFactory: async (options: RedisClientOptions) => {
const dataSource = new RedisDataSource(options);
await dataSource.connect();
return dataSource;
},
inject: [REDIS_CONNECTIONS_OPTIONS_TOKEN],
scope: Scope.TRANSIENT,
},
CacheService,
],
TRANSIENT scope does not work here either.
But when I see the redis connection instance in the debugger, it shows database as 0, in both the modules.
INQUIRER in the CacheService returns undefined, I think it is being created in the default scope.
can you show us exactly how you're importing that dynamic module twice?
each module that had imported the dynamic one will receive a brand new instance of RedisDataSource if you have that scope: Scope.TRANSIENT added to RedisDataSource
And since CacheService is injecting RedisDataSource, it will becomen transient as well automatically
I'm importing it like this
CacheModule.registerAsync({
imports: [ConfigsModule],
useFactory: (configService: ConfigService) => {
return {
socket: {
host: configService.getOrThrow('REDIS.HOST'),
port: configService.getOrThrow('REDIS.PORT'),
},
database: DATABASE_NUMBER,
};
},
inject: [ConfigService],
}),
just changing the DATABASE_NUMBER in each of the module which imports
so there is no way that DATABASE_NUMBER is the same on each call, right?
no, it is a constant which is declared in the module file itself and is not exported from it.
I got this when using scope: Scope.TRANSIENT in a custom provider that was injected into a provider with no explicit scope defined (called Foo)
then I got this when using @Injectable({ scope: Scope.TRANSIENT })
and this is expected, as the docs says
I just read
now I'll see if that works the same when using factory providers
Okay, so I am supposed to get the same RedisDataSource instance even if scope is marked TRANSIENT?
I have marked every dependency as TRANSIENT, even the cache service, do I have to mark the services which use cache service as TRANSIENT too?
I guess this a bug
using useValue, it works as I expect
but switching to useFactory, it doesn't 🤔
I'll checkout old versions of @nestjs/core to see when this has been changed
But feels really weird to me
in your case, just marking CacheService should be enough
yes, I'm using grpc microservice and it is breaking if I mark the controllers and services which use cache service as TRANSIENT
thanks
to be really fair, you don't need to use transient scope here
instead, if you really want one exclusive instance for each .register() call, you can hacky it by adding some sort of random ID to the object returned by .register()
for example:
I can drop the scope: Scope.TRANSIENT line as well
nope it still behaves the same way:
static registerAsync(moduleOptions: CacheModuleOptions): DynamicModule {
return {
module: CacheModule,
imports: moduleOptions.imports,
providers: [
{
provide: REDIS_CONNECTIONS_OPTIONS_TOKEN,
useFactory: moduleOptions.useFactory,
inject: moduleOptions.inject,
// scope: Scope.TRANSIENT,
},
{
provide: RedisDataSource,
useFactory: async (options: RedisClientOptions) => {
const dataSource = new RedisDataSource(options, uuid());
await dataSource.connect();
this.redisDataSource = dataSource;
return dataSource;
},
inject: [REDIS_CONNECTIONS_OPTIONS_TOKEN],
// scope: Scope.TRANSIENT,
},
CacheService,
],
exports: [CacheService],
};
}
I passed a uuid and made it a property in the object called connectionId
It is getting logged from both the services and has the same database number
see connection id it is not changing
See if the module itself is initializing twice. Add a console.log on constructor
Oh, actually, the uuid must be generated beforehand. Move it to a value provider instead
No, just do what I did
Add a “noop” provider using useValue with some unique random value
This is enough to tell to Nest that that dynamic module is different on each call of .register
oh okay
yes, that works
can you explain a little bit why it is behaving this way?
I can’t tell yet if this is a limitation or a bug. I’ll investigate better and open a new Issue on github if needed
will you please update here the github issue or you findings if it not much overhead, I'd like to know more about this. Thanks for the help
But if I recall correctly the main issue here is that there is a caching mechanism that rely on the serialized values. And factory providers are ignored (for some intended reason). Which ends up making both of your .register calls looking the same to nestjs DI
yes, I'm using redis and it creates databases as numbers, so when connecting you have to pass a number to create a database, the provider returning the connection was being used once, even though the database number is different in the useFactory declared by the importing modules
And I'm using dynamic modules
I found that this is behaving the same since v8
so I'd say that this is rather a limitation than a bug
but I'll open a new issue on github tomorrow to see Kamil's thoughts and conclusion on this because if this is something that we won't support any time soon, it should be mentioned in the docs somewhere