5

I am new to Angular 2 and Typescript and trying to understand DI. In all the code I have seen, I see that the variable referring to a service is typed into the constructor. Why is that? Why can't we have it declared outside the constructor but inside the class?

Consider following code from the Tour of Heroes e.g. on Angular website:

import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
    moduleId: module.id,
    selector: 'my-dashboard',
    templateUrl: `dashboard.component.html`,
    styleUrls: ['dashboard.component.css']
})
export class DashboardComponent implements OnInit {

    heroes: Hero[] = [];

    constructor(private heroService: HeroService) { }

    ngOnInit(): void {
        this.heroService.getHeroes()
            .then(heroes => this.heroes = heroes.slice(1, 5));
    }
}

If I declare heroService outside the constructor like below, the app throws many errors.

export class DashboardComponent implements OnInit {

    heroes: Hero[] = [];

    constructor() { }

    private heroService: HeroService;

    ngOnInit(): void {
        this.heroService.getHeroes()
            .then(heroes => this.heroes = heroes.slice(1, 5));
    }
}

As I understand, writing it outside the constructor does not generate an instance of the service class HeroService, but why? (Is it a Angular thing or TypeScript?) In this e.g., Hero is also a class (though not a service class, but still technically a class!), and we have declared heroes: Hero[] = []; outside the constructor, and it works.

darKnight
  • 5,651
  • 13
  • 47
  • 87
  • Possible duplicate of [How to add another provider to the injector?](https://stackoverflow.com/questions/45479701/how-to-add-another-provider-to-the-injector) – Paul Sweatte Oct 04 '17 at 14:15

2 Answers2

3

Angular DI inspects the constructor parameters and when Angular DI creates a new instance of a class (service, component, directive, pipe), it looks up matching providers to be passed to the constructor.

Therefore,

  • injection only works for classed instantiated by DI

  • only constructor parameters are considered for injection

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Consider looking at the docs as well : it is well documented : https://angular.io/docs/ts/latest/guide/dependency-injection.html – Alex Beugnet Jan 18 '17 at 09:15
  • @Günter Zöchbauer: If that is the case, then in the above code, why does 'heroes: Hero[] = [];' work? (it's defined outside the constructor). Here 'Hero' is just a simple class without any decorators defined in a different file. Is the variable 'heroes' here an instance of 'Hero' Class or something else? If it's a new instance of Class 'Hero', then how did it get instantiated outside constructor? – darKnight Jan 18 '17 at 09:34
  • That's a basic TypeScript feature. `heroes: Hero[]` declares a field named `heroes` of type `Hero[]` and `= []` assigns an initial value (an empty array). This is not related to DI or Angular in any way. – Günter Zöchbauer Jan 18 '17 at 09:36
  • @Günter Zöchbauer: Aren't Interfaces more suited for doing such a thing? Because here there any no instances being created of Class 'Hero', just its types. Here is the code (https://angular.io/docs/ts/latest/tutorial/toh-pt3.html) – darKnight Jan 18 '17 at 09:46
  • I'm not that deep into TS myself. I know that interfaces are dropped at runtime, they are for static analysis only, and if the class doesn't have any methods, then it seems a class forks for this as well. I don't know the exact reasons why they used a class here instead of an interface. – Günter Zöchbauer Jan 18 '17 at 10:06
2

What you have described here is a property (setter) injection that is in theory possible but is generally not recommended aka considered as a bad practice.

To understand this a little bit better you should read about constructor injection and why it is preferred over other types of injection like property injection.

As far as I know Angular has not implemented other types to force developers to use the best practice, that's why you cannot use it outside of the constructor.

Angular 1 references this doc that you can read to understand it deeper. Don't worry that it is from previous version, these are conceptual terms that are irrelevant to the framework or language, so you can read it to understand the basics.

P.S. Don't forget that having a variable in constructor the way you have it in Typescript (with private) will automatically create a property. So when you have it in a constructor, Angular injects it there and Typescripts implicitly creates a field as in your last snippet and initializes it from the constructor parameter.

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207