#Scope.TRANSIENT provider shared between multiple injections

15 messages · Page 1 of 1 (latest)

mint gazelle
#

When injecting a Scope.TRANSIENT provider (LoggerService) into two different consumers, the instance is unexpectedly shared, even though transient providers are expected to create new instances per consumer based on the docs.

Minimal Reproduction:

import { Inject, Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService {
  public context?: string;
}

@Injectable()
export class SecondService {
  constructor(
    @Inject(LoggerService)
    public readonly loggerService: LoggerService,
  ) {
    this.loggerService.context = 'SecondService';
  }
}

@Injectable()
export class FirstService {
  constructor(
    @Inject(SecondService)
    public readonly secondService: SecondService,
    @Inject(LoggerService)
    public readonly loggerService: LoggerService,
  ) {
    this.loggerService.context = 'FirstService';
    console.log(
      this.loggerService.context,                     // 'FirstService'
      this.secondService.loggerService.context,       // also 'FirstService'
    );
  }
}
atomic mantle
#

How are you setting up the modules? And how are you firing off the console.log?

Could you create a codesandbox showing your minimum repoduction?

mint gazelle
#

The console.log is firing immediately when you start the app. Also, you can go to / to fire it again from the controller

tropic sequoia
#

https://docs.nestjs.com/fundamentals/injection-scopes#scope-hierarchy

Transient-scoped dependencies don't follow that pattern. If a singleton-scoped DogsService injects a transient LoggerService provider, it will receive a fresh instance of it. However, DogsService will stay singleton-scoped, so injecting it anywhere would not resolve to a new instance of DogsService. In case it's desired behavior, DogsService must be explicitly marked as TRANSIENT as well.

mint gazelle
# tropic sequoia https://docs.nestjs.com/fundamentals/injection-scopes#scope-hierarchy > Transien...

I'm aware of this note, but I think it's different from what I'm experiencing

Correct me if I got this wrong.

The note is saying that if I injected the FirstService multiple times DI container will provide the same instance, which is the case the note is discussing.

But in my case, I'm having an issue with the LoggerService. I'm expecting to have multiple instances, since the LoggerService is Transient scoped, the DI container should provide an instance for the FirstService, and another new instance for the SecondService

The provided Codesandbox is showing that both FirstService and SecondService are getting the same instance of the LoggerService

Is that right, or am I missing something?

tropic sequoia
#
  1. FirstService instance created
  2. SecondService dependency resolution → LoggerService instance created
  3. LoggerService dependency resolution → Existing instance reused
    Transient providers are not shared among consumers, but they are shared within the same DI context.

https://github.com/nestjs/nest/blob/63e1a83dc9a2ea39160264953db57860f2807f45/integration/injector/e2e/scoped-instances.spec.ts#L25-L37

In my case, I declare the Logger as a class field.

https://github.com/CatsMiaow/nestjs-project-performance/blob/5e39a53af5730ab6e4cb353a21e75d275a1435ee/src/sample/sample.controller.ts#L20-L21

atomic mantle
#

My understanding is also the logger service should be a new instance and it is, if SecondService is not transient.

This might be a bug.

I'd say, update your codesandbox Nest versions to be the latest (v11) and start an issue issue on Github. We'll either be told it is correct behavior and the reasoning why (hopefully) or it is a bug.

#

By the way, if this is about logging or instantiating loggers, the standard convention to bind a logger to a class is how @tropic sequoia 's code demonstrates it.

stoic echo
#

This does indeed look like a bug

tropic sequoia
mint gazelle
# atomic mantle By the way, if this is about logging or instantiating loggers, the standard conv...

I used the logger service because it's an easier example to explain.

The issue happened to me while building the "Unit of Work" pattern.
The unit of work is a transient provider that also has multiple transient repositories inside it.
Then there is a business logic service that has to inject both the unit of work and some repository

Service ___ UnitOfWork ___ MyRepository
        \__ MyRepository

And since the MyRepository instance is the same, the unit of work class tried to override the plain repository transaction session

mint gazelle
atomic mantle
tropic sequoia