How to use Dependency Injection (DI) correctly in Angular2?

Solution 1:

Broad question, TL;DR version


@Injectable()

  • is a decorator which tells the typescript that decorated class has dependencies and does not mean that this class can be injected in some other.

  • And then TypeScript understands that it needs to Inject the required metadata into decorated class when constructing, by using the imported dependencies.

bootstrap(app, [service])

  • bootstrap() takes care of creating a root injector for our application when it’s bootstrapped. It takes a list of providers as second argument which will be passed straight to the injector when it is created.

  • You bootstrap your application with the services that are gonna be used in many places like Http, which also means you'll not need to write providers: [Http] in your class configuration.

providers: [service]

  • providers also does the work of passing all the services' arguments to Injector .

  • You put services in providers if it's not bootstrap()ped with. And is needed only in a few places.

@Inject()

  • is also a decorator a function that does the work of actually injecting those services
    like this. constructor(@Inject(NameService) nameService)
  • but if you use TS all you need to do is this constructor(nameService: NameService) and typescript will handle the rest.

Further Reading

  • If you want to dig deep in DI, take a look at this amazing article

  • and to understand Decorators vs Annotations see this.

  • Here is the official guide.

  • Gunter's Answer Mark Rajcok's Answer and Accepted Answer

Hope this helps. :)

Solution 2:

Dependency injection in Angular2 relies on hierarchical injectors that are linked to the tree of components.

This means that you can configure providers at different levels:

  • For the whole application when bootstrapping it. In this cases, all sub injectors (the component ones) will see this provider and share the instance associated with. When interacting, it will be the same instance
  • For a specific component and its sub components. Same as before but for à specific component. Other components won't see this provider. If you redefine something defined above (when bootstrapping for example), this provider will be used instead. So you can override things.
  • For services. There are no providers associated with them. They use ones of the injector from the element that triggers (directly = a component or indirectly = a component that triggers the call of service chain)

Regarding your other questions:

  • @Injectable. To inject into a class, you need a decorator. Components have one (the @Component one) but services are simple classes. If a service requires dependencies to be injected in it, you need this decorator.
  • @Inject. In most times, the type of constructor parameters is enough to let Angular2 determines what to inject. In some cases (for example, if you explicitly use an OpaqueToken and not a class to register providers), you need to specify some hints about what to inject. In such cases, you need to use @Inject.

See these questions for additional details:

  • What's the best way to inject one service into another in angular 2 (Beta)?
  • Angular2: Inject a non @Injectable class
  • Inject all Services that implement some Interface

Solution 3:

I need to either use providers: []

For dependency injection to be able to create instances for you, you need to register providers for these classes (or other values) somewhere.

Where you register a provider determines the scope of the created value. Angulars DI is hierarchical.
If you register a provider at the root of the tree


>=RC.5

@NgModule({
  providers: [/*providers*/]
  ...
})

or for lazy loaded modules

static forRoot(config: UserServiceConfig): ModuleWithProviders {
  return {
    ngModule: CoreModule,
    providers: [
      {provide: UserServiceConfig, useValue: config }
    ]
  };
}

<=RC.4

(bootstrap(AppComponent, [Providers}) or @Component(selector: 'app-component', providers: [Providers]) (root component)


then all components and services that request an instance get the same instance.

If a provider is registered in one of the child components a new (different) instance is provided for descendants of this component.

If a component requests an instance (by a constructor parameter), DI looks "upwards" the component tree (starting from leaf towards the root) and takes the first provider it finds. If an instance for this provider was already created previously, this instance is used, otherwise a new instance is created.

@Inject()

When a component or service requests a value from DI like

constructor(someField:SomeType) {}

DI looks up the provider by the type SomeType. If @Inject(SomeType) is added

constructor(@Inject(SomeType) someField:SomeType) {}

DI looks up the provider by the parameter passed to @Inject(). In the above example the parameter passed to @Inject() is the same as the type of the parameter, therefore @Inject(SomeType) is redundant.

However there are situations where you want to customize the behavior for example to inject a configuration setting.

constructor(@Inject('someName') someField:string) {}

The type string isn't sufficient to distinguish a specific configuration setting when you have a several registered.
The configuration value needs to be registered as provider somewhere like


>=RC.5

@NgModule({
  providers: [{provide: 'someName', useValue: 'abcdefg'})]
  ...
})
export class AppModule {}

<=RC.4

bootstrap(AppComponent, [provide('someName', {useValue: 'abcdefg'})])

Therefor you don't need @Inject() for FormBuilder if the constructor looks like

constructor(formBuilder: FormBuilder) {}