In a stark departure from Angular 1.x, Angular 2 features a hierarchical injection scheme. This has a substantial number of implications, and one of the more prominent one is the ability to control when, and how many, services are created.
The code, links, and a live example of this are available at .
Suppose you begin with the following simple application:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <h1>root component!</h1> <article></article> <article></article> ` }) export class RootComponent {} [app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: ` <p>Article component!</p> ` }) export class ArticleComponent {} [app/article.service.ts] import {Injectable} from '@angular/core'; @Injectable() export class ArticleService { constructor() { console.log('ArticleService constructor!'); } } [app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RootComponent} from './root.component'; import {ArticleComponent} from './article.component; @NgModule({ imports: [ BrowserModule, ], declarations: [ RootComponent, ArticleComponent ], bootstrap: [ RootComponent ] }) export class AppModule {}
Your objective is to inject a single instance of ArticleService
into the two child components. In this recipe, console.log
inside the ArticleService
constructor allows you to see when one is instantiated.
Begin by importing the service into AppModule
, then providing it with the following:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RootComponent} from './root.component'; import {ArticleComponent} from './article.component; import {ArticleService} from './article.service; @NgModule({ imports: [ BrowserModule, ], declarations: [ RootComponent, ArticleComponent ], providers: [ ArticleService ] bootstrap: [ RootComponent ] }) export class AppModule {}
Since ArticleService
is provided in the same module where ArticleComponent
is declared, you are now able to inject ArticleService
into the child ArticleComponent
instances:
[app/article/article.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; @Component({ selector: 'article', template: ` <p>Article component!</p> ` }) export class ArticleComponent { constructor(private articleService_:ArticleService) {} }
With this, you will find that the same service instance is injected into both the child components as the ArticleService
constructor, namely console.log
, is only executed once.
As the application grows, it will make less and less sense to cram everything into the same top-level module. Instead, it would be ideal for you to break apart modules into chunks that make sense. In the case of this recipe, it would be preferable to provide ArticleService
to the application pieces that are actually going to inject it.
Define a new ArticleModule
and move the relevant module imports into that file instead:
[app/article.module.ts] import {NgModule} from '@angular/core'; import {ArticleComponent} from './article.component'; import {ArticleService} from './article.service'; @NgModule({ declarations: [ ArticleComponent ], providers: [ ArticleService ], bootstrap: [ ArticleComponent ] }) export class ArticleModule {}
Then, import this entire module into AppModule
instead:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RootComponent} from './root.component'; import {ArticleModule} from './article.module'; @NgModule({ imports: [ BrowserModule, ArticleModule ], declarations: [ RootComponent ], bootstrap: [ RootComponent ] }) export class AppModule {}
If you stop here, you'll find that there are no errors, but AppModule
isn't able to render ArticleComponent
. This is because Angular modules, like other module systems, need to explicitly define what is being exported to other modules:
[app/article.module.ts] import {NgModule} from '@angular/core'; import {ArticleComponent} from './article.component'; import {ArticleService} from './article.service'; @NgModule({ declarations: [ ArticleComponent ], providers: [ ArticleService ], bootstrap: [ ArticleComponent ], exports: [ ArticleComponent ] }) export class ArticleModule {}
With this, you will still see that ArticleService
is instantiated once.
Angular 2's dependency injection takes advantage of its hierarchy structure when providing and injecting services. From where a service is injected, Angular will instantiate a service wherever it is provided. Inside a module definition, this will only ever happen once.
In this case, you provided ArticleService
to both AppModule
and ArticleModule
. Even though the service is injected twice (once for each ArticleComponent
), Angular uses the providers
declaration to decide when to create the service.
At this point, a curious developer should have lots of questions about how exactly this injection schema behaves. There are numerous different configuration flavors that can be useful to the developer, and these configurations only require a minor code adjustment from the preceding result.
As you might anticipate from the preceding explanation, you can reconfigure this application to inject a different ArticleService
instance into each child, two in total. This can be done by migrating the providers
declaration out of the module definition and into the ArticleComponent
definition:
[app/article.module.ts] import {NgModule} from '@angular/core'; import {ArticleComponent} from './article.component'; @NgModule({ declarations: [ ArticleComponent ], bootstrap: [ ArticleComponent ], exports: [ ArticleComponent ] }) export class ArticleModule {} [app/article.component.ts] import {Component} from '@angular/core'; import {ArticleService} from './article.service'; @Component({ selector: 'article', template: ` <p>Article component!</p> `, providers: [ ArticleService ] }) export class ArticleComponent { constructor(private articleService_:ArticleService) {} }
You can verify that two instances are being created by observing the two console.log
statements called from the ArticleService
constructor.
The location of the providers also means that service instance instantiation is bound to the lifetime of the component. For this application, this means that whenever a component is created, if a service is provided inside that component definition, a new service instance will be created.
For example, if you were to toggle the existence of a child component with ArticleService
provided inside it, it will create a new ArticleService
every time ArticleComponent
is constructed:
[app/root.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <h1>root component!</h1> <button (click)="toggle=!toggle">Toggle</button> <article></article> <article *ngIf="toggle"></article> ` }) export class RootComponent {}
You can verify that new instances are being created each time ngIf
evaluates to true
by observing additional console.log
statements called from the ArticleService
constructor.