Nest: Why decorator name is Component and not Service?

Created on 18 Nov 2017  路  18Comments  路  Source: nestjs/nest

@kamilmysliwiec :)

Most helpful comment

Additional food for thought, Component breaks component-class-suffix tslint rule if you are also putting your angular app in the same repo, which is annoying.

i am using the import { Component as Injectable } . work around rather than adding tslint:disable-next-line everywhere, but i think you should rethink how valuable connivence is to the users of your library over naming conventions.

All 18 comments

Yeah I had this same question. Why not go with just Injectable like angular?

I'm Asking myself the same question every time I create a new Service 馃檮

I suppose it's done because component is not only service. If it is named as @Service then this class should correspond mainly to service layer. Component annotation is some base annotation. Probably in future, if something is marked as @Service instead of @Component it will do some additional job. It's just my guess. I supposed it based on how it is done in spring framework. Service is extending Component there. And here is the link https://stackoverflow.com/questions/6827752/whats-the-difference-between-component-repository-service-annotations-in

@Target(value=TYPE)
 @Retention(value=RUNTIME)
 @Documented
 @Component
public @interface Service

Still it is designed to be injected so it should just be @Injectable like angular

Hi 馃檪
@Jamakase explained very well why the name is a @Component() instead of @Service().

@wbhob according to the @Injectable(), a few months ago I had to decide which name fits better, @Component() or @Injectable(). To make the transition between Angular and Nest, a lot of things work similar, also the names of the decorators, metadata properties are also almost exactly same. I finally decided to choose @Component() because the components work in Nest like components in Angular. The Angular injectables (providers) are registered in the global scope, once declared may be injected across entire application - they can't be encapsulated inside modules = they don't have to be exported from them. Components, on the other hand, are available only within the current scope (module) and to use them outside - we need to import+export them. That's how the Nest components work in fact. I think the providers property + @Injectable() decorator could create a lot of confuses. However, if someone still doesn't like it, it's always possible to create an alias 馃槂

@kamilmysliwiec what's the nestjs version of an injectable service? Do I always have to export it? Why did you decide against the "Angular Way" of DI which is well understood?

@Kamshak Yes, you always have to export it if you want to use it outside. The Nest way is different because the requirements of the backends systems are different than the front-end applications. I decided to make possible to use techniques such as for example Domain-Driven Design to restrict the reach of the components, allow to create bounded contexts. The Nest modules are some kind of the facades, packages which should encapsulate the logic inside them itself and export only these components, which should have an access to this 'restricted' external API. It just gives more capabilities, and Angular approach doesn't fit there, but in front-end apps, it's definitely a best choice.

I see. I'd like to ask some more to understand the decision a bit better:

But then why use dependency injection at all? What's the benefit if you can't swap the implementations. I thought the idea is to define the interfaces and allow to inject implementations depending on the context. This still has clear domain boundaries but you can mock things, test them etc. Am I missing something here?

Personal opinion:
After trying this framework (which i otherwise like very much) i won't use it for another app since it's really diffcult to keep in mind different dependency injection systems and names for very very similar concepts. Leads to a lot of frustration. The strength i saw in this framework was that I can apply everything I already know from angular and apply it to the backend (get it up really quickly!). And that the same concepts apply on client and server. Naturally backend and frontend will share the same domain and within a microservices context a really strict way to enforce the domain boundaries is to just create a completely separate service.

Only an opionion of course but it does seem to be why others come here as well - if the framework goes into a different direction that's fine ofc but i see a huge opportunity here :) Maybe this is not at all the goal though. Although it would be really simple to implement angular's injection on the backend: https://github.com/mgechev/injection-js

But then why use dependency injection at all?

I don't see any connection at all.

I thought the idea is to define the interfaces and allow to inject implementations depending on the context. 

Yeah, you can still override the implementation inside the different module depending on the context using the following syntax:

provide: ClassX
useClass: ClassY

The reason is that TypeScript interfaces don't generate any interfaces metadata. They completely disappear after transpilation thereby we can't create e.g. injection by the interface, unfortunately. I still have a hope that this will be available someday, so we can make the framework even more powerful. 馃槂

Btw, in fact, Nest has an inverted 'context management system'. You don't always have to export services, but it's a little bit more complicated pattern described here https://docs.nestjs.com/advanced/hierarchical-injector and here's an example https://github.com/nestjs/nest/tree/master/examples/04-injector

About the injection system, it won't change in the future (hope you aren't angry), and I still think that registering everything as a global and making available from each place is not a good practice here.

I believe there is a bit of a misunderstanding here I think we mean very similar things!

I don't see any connection at all.
You're right, i confused it and thought there would be no way to swap a child module's implementation from a parent. If that is possible that's all good.

In Angular2 @Injectables() are not global - they are scoped to the module they are defined in. You always need to add them to the providers of some module. If you define them in a parent all of the children can use the provided instance (which makes sense). See here for a nice description: https://vsavkin.com/dependency-injection-in-angular-1-and-angular-2-d69589979c18 or the official guide (imo the blog post is better though) https://angular.io/guide/hierarchical-dependency-injection.

For interfaces:
What i meant was just that a class represents an interface. Since in typescript you can just do

class ClassA {
  bla() { 
    // class a impl
  }
  blub() { }
}

class ClassB implements ClassA {
  bla() { 
    // class b impl
  }
  blub() { }
}

or just use an abstract class (and call it interface) this is fine imo :)

What i'm really missing is a way to define arbitrary objects and factories through tokens though (BTW this solves the issue of interfaces not having types to some extent as well!):

export const TOKEN_A = new InjectionToken("Token A");
// some module: {provide: TOKEN_A, useValue: 123123}
// some consumer: @Inject(TOKEN_A) private a: number

or say i want to inject a function
{ provide: TRANSFORMATION_FN, useValue: a => a*2 }

this allows reflection to work and since it uses object instances as tokens it'll work even if the strings are the same. Factories are quite nice as well as it's often overkill to create a class. (see here https://angular.io/guide/dependency-injection#non-class-dependencies).

About the injection system, it won't change in the future (hope you aren't angry), and I still think that
registering everything as a global and making available from each place is not a good practice here

Absolutely! Not angry at all :smile: I think the idea for this framework is fantastic and there are nice implementations of many ideas. Choosing the right areas to innovate in is really imporant IMO, and to me that would not be DI for which we have a really nice, performant, well tested and understood implementation readily available.

Maybe hopefully some confusion is cleared up and you understand a bit better what I mean? The angular 1 way of making everything globally available just because it's used somewhere in the module is pretty shite tbh. The Angular 2 way is pretty much what you are doing. I really hope there could be some exchange there!

@Kamshak I think you should definitely take a look here https://docs.nestjs.com/advanced/dependency-injection 馃槃 (possibility to inject through token - values, factories and different classes is here!)

Completely missed that! Ty :) Somehow since the field was called components instead of providers and my Auto Import didn't find anything like OpaqueToken or InjectionToken I just assumed that it wasnt there :man_facepalming: . Time to rewrite some code...

@Kamshak maybe it's time to create sth like that 馃 (I mean e.g. InjectionToken class)

import { Component as Injectable } ... , why I'm not happy writing this? 馃槶

Because that鈥檚 improper @BruceHem. Stick to the framework way, until it is or isn鈥檛 changed

So why can't "Components" (if injected in a parent module) be applied to child modules again?

Additional food for thought, Component breaks component-class-suffix tslint rule if you are also putting your angular app in the same repo, which is annoying.

i am using the import { Component as Injectable } . work around rather than adding tslint:disable-next-line everywhere, but i think you should rethink how valuable connivence is to the users of your library over naming conventions.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings