1

Going through this todo tutorial and it has the following:

 import {TodoDataService} from './todo-data.service';

 @Component({
  // ...
   providers: [TodoDataService]
 })

 constructor(private todoDataService: TodoDataService) {
 }

IIUC we have to declare the TodoDataService in the providers array. This seems redundant. Could the Angular DI read the generated metadata for the component and automatically inject the TodoDataService using constructor injection?

Update

Angular has implemented this to some extent now. I asked them to remove providedIn:root as well and they said they have things on the roadmap for that.

Ole
  • 29,797
  • 32
  • 110
  • 232
  • Somewhere you need to tell it what should be injected for that token; either in the module's providers array, or the component's providers array. – jonrsharpe Jun 02 '17 at 19:43
  • Spring made it automatic in 4.3. I'm wondering whether it would be simpler for angular to just default to a similar type of mechanism, and if more detail / customization is needed then allow for further customization: https://spring.io/blog/2016/03/04/core-container-refinements-in-spring-framework-4-3 – Ole Jun 02 '17 at 19:50
  • Also opened Angular issue: https://github.com/angular/angular/issues/17182 – Ole Jun 02 '17 at 19:52
  • Yes, it could read. No, it shouldn't read. Having no control on what's injected would result in enormously bad experience for the developer. There are hierarchical injectors. There are multiple provider types. This would be just a nightmare. – Estus Flask Jun 02 '17 at 19:52
  • Why does it work for Spring? – Ole Jun 02 '17 at 19:52
  • I don't do Spring and can't say for sure that this behaviour *really works* for it. But it would be a disaster in hierarchical injector. Considering that you expect `todoDataService: TodoDataService` to be a root injector singleton and you forgot to specify `providers: [TodoDataService]` in module there, would you be happy to find that it isn't a singleton after several hours of tedious debugging? It supposed to throw a meaningful DI error... but then we fixed it, enjoy the magic ))) – Estus Flask Jun 02 '17 at 20:31
  • The reason it works for Spring is that 99.9999999999% of the time all we want is a singleton. Angular would be much friendlier and efficient to work with if the default configuration was automatic. You just make the hierarchical injector provide a default singleton instance at the top of the hierarchy. This gets automatically injected to all constructors that need it. If the designer needs more control, then they can override. – Ole Jun 02 '17 at 20:35
  • It most certainly absolutely wouldn't. There are moments when a dev can afford to be lazy, but it isn't one of them. 'Default' singleton may be harmful. Or it may be not achievable at all, just because components have local compiler deps that aren't available in root injector. It looks like you're pretty new to A2+. I would suggest to get hands dirty with real-world tasks with framework before jumping to conclusions. – Estus Flask Jun 02 '17 at 20:57
  • You are saying **may** be harmful .... but 99.99999999% of the time its exactly what the developer wants. And 99.99999999999% of the time it is achievable. Angular can simply scan the dependency code, wire up the constructor injected singletons, and the code application will have an easy to understand predictable structure. Beyond just making development more efficient and pleasant, this also has benefits for maintenance and testing: https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4 – Ole Jun 02 '17 at 21:07
  • Where did you get this number? Did you extrapolate your own Spring experience? They are oranges and apples. There are a lot of JS specific problems you would never experience in Java. There are a lot of Angular specific problems. I appreciate the enthusiasm you're having with new ideas for a fresh tool, but you're going in the wrong direction. I'm rather disciplined dev, and I had at least a dozen of times when `Can't resolve all parameters for ...` DI error was really helpful for me. Just give yourself half a year of good Angular experience and then give that 'autowire' idea a fresh look. – Estus Flask Jun 02 '17 at 21:25
  • It's not that hard to conclude that if the constructor requires a service then 99.99999999% of the time what the application wants is a singleton instance of the service that it can inject the component needing the instance with. – Ole Jun 02 '17 at 21:25
  • @estus so far you have not provided a single concrete simple example of what it is you are talking about. The simplest thing a developer can do is wire up the application with singleton services that are easily testable and mockable via constructor injection. If you have deeper insight then please feel free to share it with an answer. Just show us one example where singleton default construction and injection is problematic and the debate is over. – Ole Jun 02 '17 at 21:36
  • The question is primarily opinion based and based on wrong supposition, so there can't be a proper answer for it. You can take my or anyone's else word on it or not, it's up to you.The good example is when you have bundled two different copies of same NPM package (it can be even one of core Angular packages), due to wrong version constraints or occasional package duplication. This happens. And this results in `Service` class in one place `!== Service` class in another. It's debugged easily with DI error but without it... it can take an eternity. – Estus Flask Jun 02 '17 at 21:49
  • Two different copies of the same npm package can be easily detected and dealt with at compile time. – Ole Jun 02 '17 at 21:54
  • One simple verifiable example from you would really help. – Ole Jun 02 '17 at 22:02
  • Yes, it's especially simple post factum when you know what you're looking for. This was an example and it was a real one. I don't feel like I have the moral right to cast doubt on that 99.999 axiom more than that. – Estus Flask Jun 02 '17 at 22:08
  • This is really simple. When we import services we import them from a specific place. Either in an NgModule or a component. Since we import them from a specific place angular can scan for these and create singleton instances of what we need. This is the simplest most straight forward way to work with DI applications. If we need further insight we can look at the container to see what the runtime contains. – Ole Jun 02 '17 at 22:13
  • Again, this is retroactive judgement, you already know the answer. IRL this would be a complex bug, and a dev may have no clue that this has something to do with dupe npm packages or DI. This is the same kind of bad thing as JS loose mode, only less predictable and harder to debug. Loose mode can be handled with 'use strict', and bad decision can be handled by not doing it. It's great that you don't other authorities but your own... but the thing is so-called authority is supposed to come with real Angular experience. Which you obviously don't have. – Estus Flask Jun 03 '17 at 09:24
  • I love a good programming discussion, but this one doesn't look constructive or entertaining. That Spring 99.999 argument is biased and total guesswork, because it is based on assumption that it's a good pattern and will surely fit Angular because you think that it will. Looks like you already decided this for yourself. – Estus Flask Jun 03 '17 at 09:30
  • Again come up with one real simple verifiable example of why the pattern does not fit and the discussion is over. Just in case you are wondering what simple and verifiable means here's an example: https://stackoverflow.com/questions/44165084/publishing-typescript-2-3-modules-to-npm-for-angular-4-consumption – Ole Jun 03 '17 at 15:07
  • Also it does not matter whether it's 99.99% or 90% or 80%, the point is that if we can eliminate boilerplate early on in the project development process, without any drawbacks, and retain the additional benefits from cleaner design and testing then we are ahead in the game. This is clearly the case here. Zero drawbacks, cleaner design, more efficient development. If you doubt it then show one simple example that proves this wrong. – Ole Jun 03 '17 at 15:11
  • I consider closing vote a helpful action, since the question is opinion-based, and your urge for biased ranting about the framework you don't have expertise with doesn't really leave the answerers a chance to give you answers you would be happy with. Nice language, by the way. – Estus Flask Jun 03 '17 at 15:58
  • Right since all you can do is offer an opinion that makes the question opinion based. – Ole Jun 03 '17 at 18:51

1 Answers1

3

From the Angular.io docs:

Injector bubbling

When a component requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector. If the component's injector lacks the provider, it passes the request up to its parent component's injector. If that injector can't satisfy the request, it passes it along to its parent injector. The requests keep bubbling up until Angular finds an injector that can handle the request or runs out of ancestor injectors. If it runs out of ancestors, Angular throws an error.

My understanding of this is that if your component provides a service, you get one instance of that service per component. If it doesn't, it walks up the component tree until it finds something that does provide it. So something has to provide it.

I think it actually does check the generated metadata, but it is using it only to see what service the component needs, not where the service comes from (which is what providers:[TodoDataService] is for).

In my application, most of my services are provided in my app.module.ts, so I get one instance of each service throughout the whole app.

uber5001
  • 3,394
  • 3
  • 19
  • 41
  • I understand. I'm just curious whether Angular could automatically perform constructor injection since it generates metadata for the component? Provided more details in the github issue. Almost always all we want is a singleton instance of a service injected via constructor injection. Angular generates metadata for components, so it could read the decorators and figure out that this is what the designer intended without requiring further configuration. – Ole Jun 02 '17 at 20:13
  • It could, but that's probably a bad design decision. If you need a global singleton, isn't that just a static class? If you need a singleton per component, isn't that just an instance? DI seems to be for everything in between. – uber5001 Jun 02 '17 at 20:40
  • I do understand the desire to not have a huge list of providers though. You should format this as a feature request on the angular/angular github. – uber5001 Jun 02 '17 at 20:41
  • Also global singletons could be static classes, but in the case of services they will most likely be instances of something that require initialization, like a GraphQL provider, PouchDB provider, etc. – Ole Jun 03 '17 at 15:18
  • For static classes containing utility methods we don't really need DI at all. We should just import these instances from libraries like RxJS, etc. I wrote up an example on how to go about producing vanilla typescript libraries here: https://stackoverflow.com/questions/44165084/publishing-typescript-2-3-modules-to-npm-for-angular-4-consumption – Ole Jun 03 '17 at 15:29
  • Another option to get one instance of a service throughout the whole app is to annotate it's definition with `@Injectable({providedIn: 'root'})`. This obviously only applies to the services your define yourself. See https://angular.io/guide/architecture-services#providing-services for ways to register a service provider with an injector. – Alex Yursha Aug 31 '18 at 23:24