Search code examples
nestjsinjectable

is not Injectable decorator required in providers?


I'm studying NestJS with a sample app.

https://github.com/nestjs/nest/tree/master/sample/12-graphql-schema-first

However, what I am curious about is that even if the service does not have an injectable decorator, it can be registered as a provider of the module, and the constructor of other providers can use the registered provider without the injectable decorator.

Actually, I removed the injectable decorator from src/cats/cats.service.ts in the example above. But it works fine.

Even without the Injectable decorator, the same object was passed to the provider's constructor.

Why is the injectable decorator necessary?


Solution

  • The answer here is: "it depends".

    If a provider has no injected dependencies, then technically, no you don't need the @Injectable() decorator. What that decorator is doing under the hood is forcing Typescript to emit constructor parameter metadata. Nest will read that metadata at runtime to know what is injected into the provider. This PR goes into more depth on the @Injectable() decorator itself

    If we have the following classes

    @Injectable()
    export class Foo {
      constructor(private readonly foo: string) {}
    }
    @Injectable()
    export class Bar {
      constructor(private readonly bar: string) {}
    }
    @Injectable()
    export class FooBar {
      constructor(private readonly foo: Foo, private readonly bar: Bar) {}
    }
    

    Then we get a compiled output like this

    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };
    var __metadata = (this && this.__metadata) || function (k, v) {
        if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
    };
    let Foo = class Foo {
        constructor(foo) {
            this.foo = foo;
        }
    };
    Foo = __decorate([
        Injectable(),
        __metadata("design:paramtypes", [String])
    ], Foo);
    export { Foo };
    let Bar = class Bar {
        constructor(bar) {
            this.bar = bar;
        }
    };
    Bar = __decorate([
        Injectable(),
        __metadata("design:paramtypes", [String])
    ], Bar);
    export { Bar };
    let FooBar = class FooBar {
        constructor(foo, bar) {
            this.foo = foo;
            this.bar = bar;
        }
    };
    FooBar = __decorate([
        Injectable(),
        __metadata("design:paramtypes", [Foo, Bar])
    ], FooBar);
    export { FooBar };
    

    The __metadata('design:paramtypes', []) is what Nest eventually reads, and matches to the DI container injection tokens