#Clarification about @Injectable

17 messages · Page 1 of 1 (latest)

clever pollen
#

Hello,

As the title says, I'd love to understand better what @Injectable actually do and when it is necessary.

From experimenting it seems to only be needed when the class (annotated with it) needs to have its dependencies injected. If the class does not need DI is it correct that @Injectable is not needed ?

I was a bit confused as I also thought that annotating a class with @Injectable was needed to have the class itself be injected elsewhere but from experimenting it does not seems right.

I am also confused about injection scopes as they are demonstrated here https://docs.nestjs.com/fundamentals/injection-scopes#usage. AFAIK a custom provider has noting to do with the @Injectable decoration, it just means that the consumer of that provider might want to opt out of the classic DI (so what determines if a provider is "custom" or not, lies on the consumer side). Thus I'm not sure to understand how the 2 examples are linked. When I want to adjust my injection scope do I need to configure this on the provided class itself (i.e. the service) using the @Injectable decorator ? Or do I need to configure this at the consumer level using the more verbose approach for custom providers ?

That's pretty much it, thanks a lot for the help 🙏

grizzled scarab
#

If the class does not need DI is it correct that @Injectable is not needed ?

Correct.

it just means that the consumer of that provider might want to opt out of the classic DI

There isn't really an "opting out" of using DI, but rather an instantiation method, which are the three injection scopes plus the durable request scope.

Thus I'm not sure to understand how the 2 examples are linked.

The differences between the two are,

  1. Using the default method of constructor injection (i.e. with @Injectable().

or

  1. Using the custom provider setup, which doesn't use the default method of constructor injection. If you need to get these providers instantiated with any other method than DEFAULT(i.e. singleton only), then you have to put the scope definition in their module entry (because constructor injection is missing).
clever pollen
# grizzled scarab > If the class does not need DI is it correct that @Injectable is not needed ? ...

Thanks I'm trying to process your answer.

which are the three injection scopes plus the durable request scope.

What is this additional durable request scope ? The 3 injection scopes you mention are the DEFAULT, REQUEST and TRANSIENT ones right ?

I'm not sure to understand how the two approaches are meant to be used (regarding the injection scopes).

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

// Not needed since the `CatService` does not need DI
// but I want to specify that a dedicated `CatsService`
// must be instanciated PER request.
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}

Using this service above 👆 , what happens if in my CastModule I do:

{
  provide: 'CAST_SERVICE',
  useClass: CastService,
  // Here I sort of overwrite the scope and now say `CatsService` is a singleton.
  scope: Scope.DEFAULT,
}
grizzled scarab
#

What is this additional durable request scope ?

https://docs.nestjs.com/fundamentals/injection-scopes#durable-providers

Using this service above 👆 , what happens if in my CastModule I do:

You'll probably get an error. Not sure. It doesn't make any sense in doing this. You can also do the scoping for a constructor based injection via the module provider definition. It's just not "normal".

clever pollen
grizzled scarab
#

Instead of doing this:

@Injectable({ scope: Scope.REQUEST })
export class CatsService {}

you could do this:

{
  provide: 'CATS_SERVICE',
  useClass: CatsService,
  scope: Scope.REQUEST,
}
#

And, you should avoid doing request or transient or even durable scoping, if you can.

clever pollen
# grizzled scarab Instead of doing this: ```ts @Injectable({ scope: Scope.REQUEST }) export class ...

Yes that's sort of my point. What is the point of configuring the injection scope when defining the service ? It should be the consumer of the service that knows what scope it needs.

Here is a the PoC (which compiles and runs fine):

import { Module, Scope } from '@nestjs/common';
import { ScopesController } from './scopes.controller';
import { ScopesService } from './scopes.service';

@Module({
  controllers: [ScopesController],
  providers: [
    {
      provide: ScopesService,
      useClass: ScopesService,
      scope: Scope.DEFAULT,
    },
  ],
})
export class ScopesModule {}
import { Controller, Get } from '@nestjs/common';
import { ScopesService } from './scopes.service';

@Controller('scopes')
export class ScopesController {
  constructor(private readonly scopesService: ScopesService) {}

  @Get()
  scopes() {
    return this.scopesService.getValue();
  }
}
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class ScopesService {
  #value: number;

  constructor() {
    this.#value = Math.random();
  }

  getValue() {
    return this.#value;
  }
}

So basically I'm defining my ScopesServices as being per request while I'm telling it is a singleton at the module level.
In my service I use a random value in the cnstructor to detect when a new instance is created out of it.
In my controller I simply return the service random value.

After experimenting I can tell the same value is returned over and over showing that the DEFAULT scope won.

#

If in my module I now use the basic DI approach: providers: [ScopesService], a new value is returned each time, showing that the service is now instantiated per request.

grizzled scarab
#

After experimenting I can tell the same value is returned over and over showing that the DEFAULT scope won.

Yes, so obviously the module definition scoping takes priority.

It should be the consumer of the service that knows what scope it needs.

Absolutely not! The provider always determines its own scope, either via the @Injectable() decorator OR the module definition scoping. Although @Injectable() is determining that the class can be injected with other providers, it is also saying that anything within that class and the class itself will be scoped to whatever you determine i.e. DEFAULT (the default), REQUEST or TRANSIENT.

#

.
In other words, the scope is only used, once that class is used as a provider itself i.e. injected.

#

The class it is being injected into is oblivious of the instantiation method.

clever pollen
# grizzled scarab > After experimenting I can tell the same value is returned over and over showin...

Although @Injectable() is determining that the class can be injected with other providers, it is also saying that anything within that class and the class itself will be scoped to whatever you determine

So that's another usecase for @Injecatble, either because it needs some DI or because you want to crontol its injection scope (or both).

n other words, the scope is only used, once that class is used as a provider itself i.e. injected.

This makes total sense.

Absolutely not! The provider always determines its own scope

~~What do people really mean by "provider" ? Do you relate to the "service" class definition itself or to the provider field of a @Module decorator ?

Because if I have 2 modules A and B and A exports ServiceA which has @Injectable(scope: Request) but the module B needs ServiceA to be a singleton for instance. Can I dot it ? (As writing it does not make sense I guess to do this~~

grizzled scarab
#

So that's another usecase for @Injecatble, either because it needs some DI or because you want to crontol its injection scope (or both).

You'd only use the @Injectable() if there are other DI providers you wish to inject. Of not, you'd use the module definition scope, as it is then a custom provider. I'd say, the scoping definition is then only a slight simplification for the developer.

A provider is basically anything that can be injected, including service classes.

clever pollen
grizzled scarab
#

You can, but you can also do it as shown, despite nothing being injected, in the expectation that something might be injected later. But yeah, the fact nothing is being injected sort of makes the example a bit convoluted.