1

As documentation says

Dependency Injection is a powerful pattern for managing code dependencies

But does Angular's DI has any sense?

Let's say we have an HeroComponent living in the HeroModule, and it uses HeroService. To use it in HeroComponent we have to:

  1. Import HeroService in HeroModule
  2. Inject HeroService in module providers
  3. Import HeroService in HeroComponent
  4. Add type or add injected service to component's parameters.

Why don't just

  1. import HeroService into HeroComponent?

We can export its instance so we can still have a singleton across our app and we can configure our webpack/karma to be still able to import mocks instead of real implementations inside our tests.

Someone can say Angular's DI makes architecture less tightly coupled, but is it true? In Angular 1.x indeed you just specified params in the constructor. But in Angular2 you have to import your dependency additionally, and you have to specify from where. So where is this loosely coupling?

Example:

import { Http } from 'angular2/http';

export class Login {
  constructor(http: Http) {
    http.whatever()
  }
}

To be able to execute our injection, we have to import it. And when we import, we define exactly what service we are going to use. I don't see any difference from above and below example:

import { http } from 'angular2/http'; (instance)

export class Login {
  constructor() {
    http.whatever()
  }}
Aravind
  • 40,391
  • 16
  • 91
  • 110
OKey
  • 182
  • 1
  • 2
  • 10
  • 1
    That's not something Angular2 invented. It's a popular pattern and considered good practice.https://en.wikipedia.org/wiki/Inversion_of_control. http://stackoverflow.com/questions/3058/what-is-inversion-of-control, http://martinfowler.com/articles/injection.html – Günter Zöchbauer Dec 11 '16 at 15:02
  • Of course, IoC is a popular pattern and good practice as it's one of SOLID principles. But it's not IoC for me if you have to manual import your dependency additionally. – OKey Dec 11 '16 at 15:04
  • There is some limitation that in TS you can't use interfaces for DI, but you can still use (abstract) base classes (that's a TS limitation, not an Angular2 limitation). I don't think importing is otherwise related to the IoC topic at all. If you want to use a type in your source code for whatever reason, you have to import it, that's not related to DI. – Günter Zöchbauer Dec 11 '16 at 15:08
  • " And when we import, we define exactly what service we are going to use." That's just wrong. If you add `providers: [{provide: Http, useClass: MyFancyHttp}]` to `@NgModule()` then you get something different than what you imported. – Günter Zöchbauer Dec 11 '16 at 15:20
  • You define this providers inside module and when you want to inject it inside your component, you add `constructor(http: Http)` and you have to get this **Http** from somewhere. So you have to import it. From where? – OKey Dec 11 '16 at 15:24
  • Right, if `Http` is not imported and not declared in the same file, it has no meaning. Importing alone also doesn't have any meaning. – Günter Zöchbauer Dec 11 '16 at 15:26
  • _Importing alone also doesn't have any meaning_ ? If you import something, you can use it. – OKey Dec 11 '16 at 15:28
  • Sure you can use it, but it doesn't say anything about if you actually want to use it or how and where you want to use it. – Günter Zöchbauer Dec 11 '16 at 15:30
  • @GünterZöchbauer could you elaborate about it? – OKey Dec 11 '16 at 15:35
  • About what? . . . . – Günter Zöchbauer Dec 11 '16 at 15:36
  • "it doesn't say anything about if you actually want to use it or how and where you want to use it" – OKey Dec 11 '16 at 15:41
  • I don't know what to elaborate about. Importing makes the type known to the current file. That doesn't tell that you want to inject it. You can also use it like `@Input() prop:SomeType;` or `constructor() { this.prop = new SomeType(); }`. What if you have a component and 2 directives in that file. Do you want to inject it to all 3? – Günter Zöchbauer Dec 11 '16 at 15:43
  • _That doesn't tell that you want to inject it_ I think it's exactly what happen under the hood. Have you tried to use Angular2 with pure es6? There you have to add this imported module into **parameters** array. – OKey Dec 11 '16 at 15:49
  • The parameters array is doing in ES6 what the constructor parameter list is doing in TS. I don't get what you try to point out. – Günter Zöchbauer Dec 11 '16 at 15:51
  • The parameters array in ES6 does what the constructor parameter's **types** do in TS. – OKey Dec 11 '16 at 15:54
  • I don't see what the comparison to the ES6 parameters array should demonstrate. – Günter Zöchbauer Dec 11 '16 at 15:57

1 Answers1

1

Dependency injection is not about importing. It's about having the instances created for you outside of your components. Without DI your HeroComponent would still need to add the import statement for the HeroService and you'd need to manually instantiate it inside HeroComponent:

myHeroService: HeroService = new HeroService();

Now imagine you need to use this service in three components. Then you'll need to write the above line in three different places.

What if you need to use a mock service because the real one is not ready yet (or you want to have a mock service for unit tests)? Without DI you'd need to modify the code in three components to something like this:

myHeroService: MockHeroService = new MockHeroService();

With DI each of the three components would have the same constructors that you'd never modify in the above scenario, e.g.:

class HeroComponent{

   constructor(heroService: HeroService){}
}

Switching to the MockHeroService would require only one change (the provider) in the HeroModule declaration:

@NgModule({
...
providers:[provide: HeroService, useClass: MockHeroService]
})

The above code would instruct Angular to create a singleton for the entire module (app). If you decide to introduce more than one instance of HeroService (say HeroComponent1 uses HeroService, and HeroComponent2 uses MockHeroService), then just declare providers:[HeroService] in the HeroComponent1:

@Component({
...
providers:[HeroService]
})

Now Angular will create a MockHeroService instance for the entire app and another instance of the HeroService for the HeroComponent1 and its children.

Keep in mind that you wouldn't need to change the constructors' signatures in any components that use this service.

Now imagine that you'd decide that in one component you want to introduce a factory function that would conditionally instantiate either HeroService or MockHeroService. You'd just declare a provider for such component using the useFactory syntax.

I can keep going :)

Yakov Fain
  • 11,972
  • 5
  • 33
  • 38
  • But when you add `constructor(heroService: HeroService){}` in the HeroComponent, you also have to import HeroService above. Otherwise compilator won't know what HeroService is. So you have to point on a specific service. – OKey Dec 11 '16 at 15:43
  • You don't point on a specific service. You point on a type that is used as a key to look up providers. The actual injected service can be a subclass of `HeroService` like the `MockHeroService` shown in the answer. – Günter Zöchbauer Dec 11 '16 at 16:15