From 94eb9ced678915ea8aa9914cb85ceb9dc6f93ff1 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Mon, 23 Oct 2017 17:44:49 -0700 Subject: [PATCH] docs: Dependency Injection guides for CLI --- .../examples/dependency-injection/plnkr.json | 3 +- .../src/app/app-config.ts | 10 + .../src/app/app.component.1.ts | 4 +- .../src/app/app.component.2.ts | 5 +- .../src/app/app.component.ts | 4 +- .../src/app/app.config.ts | 10 +- .../src/app/app.module.ts | 43 +- .../src/app/dummy.module.ts | 25 + .../src/app/heroes/dummy.module.ts | 35 + .../src/app/heroes/hero-list.component.1.ts | 9 +- .../src/app/heroes/hero-list.component.2.ts | 7 +- .../src/app/heroes/hero-list.component.ts | 9 +- .../src/app/heroes/hero.service.0.ts | 6 + .../src/app/heroes/hero.service.1.ts | 1 - .../src/app/heroes/hero.service.2.ts | 1 - .../src/app/heroes/hero.service.ts | 1 - .../src/app/heroes/heroes.component.1.ts | 17 +- .../src/app/heroes/heroes.component.ts | 9 +- .../src/app/providers.component.ts | 47 +- .../src/app/providers.module.ts | 33 + .../src/app/test.component.ts | 16 +- .../guide/dependency-injection-in-action.md | 47 +- .../guide/dependency-injection-pattern.md | 167 +++ aio/content/guide/dependency-injection.md | 970 +++++------------- aio/content/navigation.json | 9 +- 25 files changed, 609 insertions(+), 879 deletions(-) create mode 100644 aio/content/examples/dependency-injection/src/app/app-config.ts create mode 100644 aio/content/examples/dependency-injection/src/app/dummy.module.ts create mode 100644 aio/content/examples/dependency-injection/src/app/heroes/dummy.module.ts create mode 100644 aio/content/examples/dependency-injection/src/app/heroes/hero.service.0.ts create mode 100644 aio/content/examples/dependency-injection/src/app/providers.module.ts create mode 100644 aio/content/guide/dependency-injection-pattern.md diff --git a/aio/content/examples/dependency-injection/plnkr.json b/aio/content/examples/dependency-injection/plnkr.json index e8d1ab24b296b..f4cd0e9b38460 100644 --- a/aio/content/examples/dependency-injection/plnkr.json +++ b/aio/content/examples/dependency-injection/plnkr.json @@ -4,7 +4,8 @@ "files":[ "!**/*.d.ts", "!**/*.js", - "!**/*.[1,2].*" + "!**/*.[0,1,2].*", + "**/dummy.module.ts" ], "tags": ["dependency", "di"] } diff --git a/aio/content/examples/dependency-injection/src/app/app-config.ts b/aio/content/examples/dependency-injection/src/app/app-config.ts new file mode 100644 index 0000000000000..81c0479ffdf4e --- /dev/null +++ b/aio/content/examples/dependency-injection/src/app/app-config.ts @@ -0,0 +1,10 @@ +/* + Must put this interface in its own file instead of app.config.ts + or else TypeScript gives a (bogus) warning: + WARNING in ./src/app/... .ts + "export 'AppConfig' was not found in './app.config' +*/ +export interface AppConfig { + apiEndpoint: string; + title: string; +} diff --git a/aio/content/examples/dependency-injection/src/app/app.component.1.ts b/aio/content/examples/dependency-injection/src/app/app.component.1.ts index fd7043f187217..a33782dc90d1a 100644 --- a/aio/content/examples/dependency-injection/src/app/app.component.1.ts +++ b/aio/content/examples/dependency-injection/src/app/app.component.1.ts @@ -1,7 +1,5 @@ -// Early versions - // #docregion -import { Component } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', diff --git a/aio/content/examples/dependency-injection/src/app/app.component.2.ts b/aio/content/examples/dependency-injection/src/app/app.component.2.ts index 55637697f1ed4..c8a2c996b2ff7 100644 --- a/aio/content/examples/dependency-injection/src/app/app.component.2.ts +++ b/aio/content/examples/dependency-injection/src/app/app.component.2.ts @@ -1,7 +1,6 @@ // #docregion // #docregion imports -import { Component } from '@angular/core'; -import { Inject } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { APP_CONFIG, AppConfig } from './app.config'; // #enddocregion imports @@ -23,3 +22,5 @@ export class AppComponent { } // #enddocregion ctor } +// #enddocregion + diff --git a/aio/content/examples/dependency-injection/src/app/app.component.ts b/aio/content/examples/dependency-injection/src/app/app.component.ts index 1612f6402e494..f9b4dc9842731 100644 --- a/aio/content/examples/dependency-injection/src/app/app.component.ts +++ b/aio/content/examples/dependency-injection/src/app/app.component.ts @@ -4,7 +4,6 @@ import { Component, Inject } from '@angular/core'; import { APP_CONFIG, AppConfig } from './app.config'; -import { Logger } from './logger.service'; import { UserService } from './user.service'; // #enddocregion imports @@ -23,8 +22,7 @@ import { UserService } from './user.service'; - `, - providers: [Logger] + ` }) export class AppComponent { title: string; diff --git a/aio/content/examples/dependency-injection/src/app/app.config.ts b/aio/content/examples/dependency-injection/src/app/app.config.ts index f224e6e1abc82..74af1cb194abf 100644 --- a/aio/content/examples/dependency-injection/src/app/app.config.ts +++ b/aio/content/examples/dependency-injection/src/app/app.config.ts @@ -1,15 +1,13 @@ +import { AppConfig } from './app-config'; +export { AppConfig } from './app-config'; + // #docregion token import { InjectionToken } from '@angular/core'; -export let APP_CONFIG = new InjectionToken('app.config'); +export const APP_CONFIG = new InjectionToken('app.config'); // #enddocregion token // #docregion config -export interface AppConfig { - apiEndpoint: string; - title: string; -} - export const HERO_DI_CONFIG: AppConfig = { apiEndpoint: 'api.heroes.com', title: 'Dependency Injection' diff --git a/aio/content/examples/dependency-injection/src/app/app.module.ts b/aio/content/examples/dependency-injection/src/app/app.module.ts index 67ae8ae913a5f..0b5f81b9fec6b 100644 --- a/aio/content/examples/dependency-injection/src/app/app.module.ts +++ b/aio/content/examples/dependency-injection/src/app/app.module.ts @@ -1,32 +1,24 @@ +// #docplaster import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; +import { APP_CONFIG, HERO_DI_CONFIG } from './app.config'; import { AppComponent } from './app.component'; import { CarComponent } from './car/car.component'; import { HeroesComponent } from './heroes/heroes.component'; import { HeroListComponent } from './heroes/hero-list.component'; import { InjectorComponent } from './injector.component'; +import { Logger } from './logger.service'; import { TestComponent } from './test.component'; -import { APP_CONFIG, HERO_DI_CONFIG } from './app.config'; import { UserService } from './user.service'; -import { - ProvidersComponent, - Provider1Component, - Provider3Component, - Provider4Component, - Provider5Component, - Provider6aComponent, - Provider6bComponent, - Provider7Component, - Provider8Component, - Provider9Component, - Provider10Component, -} from './providers.component'; + +import { ProvidersModule } from './providers.module'; // #docregion ngmodule @NgModule({ imports: [ - BrowserModule + BrowserModule, + ProvidersModule ], declarations: [ AppComponent, @@ -35,26 +27,19 @@ import { // #enddocregion ngmodule HeroListComponent, InjectorComponent, - TestComponent, - ProvidersComponent, - Provider1Component, - Provider3Component, - Provider4Component, - Provider5Component, - Provider6aComponent, - Provider6bComponent, - Provider7Component, - Provider8Component, - Provider9Component, - Provider10Component, + TestComponent // #docregion ngmodule ], - // #docregion ngmodule-providers + // #docregion providers, providers-2 providers: [ + // #enddocregion providers + Logger, + // #docregion providers UserService, { provide: APP_CONFIG, useValue: HERO_DI_CONFIG } ], - // #enddocregion ngmodule-providers + // #enddocregion providers, providers-2 + exports: [ CarComponent, HeroesComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } diff --git a/aio/content/examples/dependency-injection/src/app/dummy.module.ts b/aio/content/examples/dependency-injection/src/app/dummy.module.ts new file mode 100644 index 0000000000000..e65fdf3c46fd6 --- /dev/null +++ b/aio/content/examples/dependency-injection/src/app/dummy.module.ts @@ -0,0 +1,25 @@ + +/// Dummy modules to satisfy Angular Language Service +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AppModule } from './app.module'; + +//////// + +import { AppComponent as AppComponent1 } from './app.component.1'; + +@NgModule({ + imports: [ CommonModule, AppModule ], + declarations: [ AppComponent1 ] +}) +export class DummyModule1 {} + +///////// + +import { AppComponent as AppComponent2 } from './app.component.2'; + +@NgModule({ + imports: [ CommonModule, AppModule ], + declarations: [ AppComponent2 ] +}) +export class DummyModule2 {} diff --git a/aio/content/examples/dependency-injection/src/app/heroes/dummy.module.ts b/aio/content/examples/dependency-injection/src/app/heroes/dummy.module.ts new file mode 100644 index 0000000000000..2e1f0017712c3 --- /dev/null +++ b/aio/content/examples/dependency-injection/src/app/heroes/dummy.module.ts @@ -0,0 +1,35 @@ + +/// Dummy modules to satisfy Angular Language Service +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +//////// + +import { HeroListComponent as HeroListComponent1 } from './hero-list.component.1'; + +@NgModule({ + imports: [ CommonModule ], + declarations: [ HeroListComponent1 ], + exports: [ HeroListComponent1 ] +}) +export class DummyModule1 {} + +///////// + +import { HeroListComponent as HeroListComponent2 } from './hero-list.component.2'; + +@NgModule({ + imports: [ CommonModule ], + declarations: [ HeroListComponent2 ] +}) +export class DummyModule2 {} + +///////// + +import { HeroesComponent as HeroesComponent1 } from './heroes.component.1'; + +@NgModule({ + imports: [ CommonModule, DummyModule1 ], + declarations: [ HeroesComponent1 ] +}) +export class DummyModule3 {} diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.1.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.1.ts index c769ddc67ed8b..5c216f4d03d04 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.1.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.1.ts @@ -1,16 +1,17 @@ // #docregion import { Component } from '@angular/core'; - import { HEROES } from './mock-heroes'; @Component({ selector: 'app-hero-list', template: ` -
- {{hero.id}} - {{hero.name}} -
+
+ {{hero.id}} - {{hero.name}} +
` }) +// #docregion class export class HeroListComponent { heroes = HEROES; } +// #enddocregion class diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.2.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.2.ts index 1f08ead95a681..11f27ed4b1a8c 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.2.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.2.ts @@ -1,7 +1,6 @@ // #docplaster // #docregion import { Component } from '@angular/core'; - import { Hero } from './hero'; // #enddocregion import { HeroService } from './hero.service.1'; @@ -15,9 +14,9 @@ import { HeroService } from './hero.service'; @Component({ selector: 'app-hero-list', template: ` -
- {{hero.id}} - {{hero.name}} -
+
+ {{hero.id}} - {{hero.name}} +
` }) export class HeroListComponent { diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.ts index 96ee3fd510879..4536a04489de3 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero-list.component.ts @@ -1,17 +1,16 @@ /* tslint:disable:one-line */ // #docregion import { Component } from '@angular/core'; - import { Hero } from './hero'; import { HeroService } from './hero.service'; @Component({ selector: 'app-hero-list', template: ` -
- {{hero.id}} - {{hero.name}} - ({{hero.isSecret ? 'secret' : 'public'}}) -
+
+ {{hero.id}} - {{hero.name}} + ({{hero.isSecret ? 'secret' : 'public'}}) +
`, }) export class HeroListComponent { diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.0.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.0.ts new file mode 100644 index 0000000000000..d496f9608c5b3 --- /dev/null +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.0.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class HeroService { + constructor() { } +} diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.1.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.1.ts index 2e0e3ca7348f1..75b12d7c44e87 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.1.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.1.ts @@ -1,6 +1,5 @@ // #docregion import { Injectable } from '@angular/core'; - import { HEROES } from './mock-heroes'; @Injectable() diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.2.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.2.ts index 6b3a98306a30d..0069b598db410 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.2.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.2.ts @@ -1,6 +1,5 @@ // #docregion import { Injectable } from '@angular/core'; - import { HEROES } from './mock-heroes'; import { Logger } from '../logger.service'; diff --git a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.ts b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.ts index fb03ec1de6ac5..02c8e4a85cfc5 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/hero.service.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/hero.service.ts @@ -1,6 +1,5 @@ // #docregion import { Injectable } from '@angular/core'; - import { HEROES } from './mock-heroes'; import { Logger } from '../logger.service'; diff --git a/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.1.ts b/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.1.ts index ded32fb995bc9..962f95fcb3243 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.1.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.1.ts @@ -1,21 +1,18 @@ // #docplaster -// #docregion full, v1 -import { Component } from '@angular/core'; +// #docregion, v1 +import { Component } from '@angular/core'; // #enddocregion v1 +import { HeroService } from './hero.service'; -import { HeroService } from './hero.service'; -// #enddocregion full - -// #docregion full, v1 - +// #docregion v1 @Component({ selector: 'app-heroes', // #enddocregion v1 - providers: [HeroService], + providers: [ HeroService ], // #docregion v1 template: ` -

Heroes

- +

Heroes

+ ` }) export class HeroesComponent { } diff --git a/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.ts b/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.ts index 047f81608547b..97040c09f0f28 100644 --- a/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.ts +++ b/aio/content/examples/dependency-injection/src/app/heroes/heroes.component.ts @@ -1,14 +1,13 @@ // #docregion import { Component } from '@angular/core'; - import { heroServiceProvider } from './hero.service.provider'; @Component({ selector: 'app-heroes', + providers: [ heroServiceProvider ], template: ` -

Heroes

- - `, - providers: [heroServiceProvider] +

Heroes

+ + ` }) export class HeroesComponent { } diff --git a/aio/content/examples/dependency-injection/src/app/providers.component.ts b/aio/content/examples/dependency-injection/src/app/providers.component.ts index 9df2975ab317b..6b40f04787911 100644 --- a/aio/content/examples/dependency-injection/src/app/providers.component.ts +++ b/aio/content/examples/dependency-injection/src/app/providers.component.ts @@ -1,19 +1,21 @@ -/* tslint:disable:one-line:check-open-brace*/ -// Examples of provider arrays -// #docplaster +/* + * A collection of demo components showing different ways to provide services + * in @Component metadata + */ import { Component, Inject, Injectable, OnInit } from '@angular/core'; -import { APP_CONFIG, AppConfig, - HERO_DI_CONFIG } from './app.config'; +import { + APP_CONFIG, + AppConfig, + HERO_DI_CONFIG } from './app.config'; -import { HeroService } from './heroes/hero.service'; -import { heroServiceProvider } from './heroes/hero.service.provider'; -import { Logger } from './logger.service'; -import { UserService } from './user.service'; +import { HeroService } from './heroes/hero.service'; +import { heroServiceProvider } from './heroes/hero.service.provider'; +import { Logger } from './logger.service'; +import { UserService } from './user.service'; -let template = '{{log}}'; +const template = '{{log}}'; -////////////////////////////////////////// @Component({ selector: 'provider-1', template: template, @@ -30,6 +32,7 @@ export class Provider1Component { } ////////////////////////////////////////// + @Component({ selector: 'provider-3', template: template, @@ -47,7 +50,7 @@ export class Provider3Component { } ////////////////////////////////////////// -class BetterLogger extends Logger {} +export class BetterLogger extends Logger {} @Component({ selector: 'provider-4', @@ -66,9 +69,10 @@ export class Provider4Component { } ////////////////////////////////////////// + // #docregion EvenBetterLogger @Injectable() -class EvenBetterLogger extends Logger { +export class EvenBetterLogger extends Logger { constructor(private userService: UserService) { super(); } log(message: string) { @@ -96,8 +100,10 @@ export class Provider5Component { } ////////////////////////////////////////// -class NewLogger extends Logger {} -class OldLogger { + +export class NewLogger extends Logger {} + +export class OldLogger { logs: string[] = []; log(message: string) { throw new Error('Should not call the old logger!'); @@ -149,11 +155,14 @@ export class Provider6bComponent { } ////////////////////////////////////////// + // #docregion silent-logger // An object in the shape of the logger service -let silentLogger = { +export function SilentLoggerFn() {} + +const silentLogger = { logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'], - log: () => {} + log: SilentLoggerFn }; // #enddocregion silent-logger @@ -172,6 +181,7 @@ export class Provider7Component { this.log = logger.logs[0]; } } + ///////////////// @Component({ @@ -189,6 +199,7 @@ export class Provider8Component { } ///////////////// + @Component({ selector: 'provider-9', template: template, @@ -218,6 +229,7 @@ export class Provider9Component implements OnInit { this.log = 'APP_CONFIG Application title is ' + this.config.title; } } + ////////////////////////////////////////// // Sample providers 1 to 7 illustrate a required logger dependency. // Optional logger, can be null @@ -248,6 +260,7 @@ export class Provider10Component implements OnInit { } ///////////////// + @Component({ selector: 'app-providers', template: ` diff --git a/aio/content/examples/dependency-injection/src/app/providers.module.ts b/aio/content/examples/dependency-injection/src/app/providers.module.ts new file mode 100644 index 0000000000000..5dc27192a8b6f --- /dev/null +++ b/aio/content/examples/dependency-injection/src/app/providers.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; + +import { + Provider1Component, + Provider3Component, + Provider4Component, + Provider5Component, + Provider6aComponent, + Provider6bComponent, + Provider7Component, + Provider8Component, + Provider9Component, + Provider10Component, + ProvidersComponent, +} from './providers.component'; + +@NgModule({ + declarations: [ + Provider1Component, + Provider3Component, + Provider4Component, + Provider5Component, + Provider6aComponent, + Provider6bComponent, + Provider7Component, + Provider8Component, + Provider9Component, + Provider10Component, + ProvidersComponent, + ], + exports: [ ProvidersComponent ] + }) + export class ProvidersModule {} diff --git a/aio/content/examples/dependency-injection/src/app/test.component.ts b/aio/content/examples/dependency-injection/src/app/test.component.ts index cbf5fe0034c8c..fcc7b5cc1dfe1 100644 --- a/aio/content/examples/dependency-injection/src/app/test.component.ts +++ b/aio/content/examples/dependency-injection/src/app/test.component.ts @@ -2,10 +2,11 @@ // Simulate a simple test // Reader should look to the testing chapter for the real thing -import { Component } from '@angular/core'; +import { Component } from '@angular/core'; -import { HeroService } from './heroes/hero.service'; -import { HeroListComponent } from './heroes/hero-list.component'; +import { Hero } from './heroes/hero'; +import { HeroService } from './heroes/hero.service'; +import { HeroListComponent } from './heroes/hero-list.component'; @Component({ selector: 'app-tests', @@ -22,12 +23,13 @@ export class TestComponent { function runTests() { // #docregion spec - let expectedHeroes = [{name: 'A'}, {name: 'B'}] - let mockService = {getHeroes: () => expectedHeroes } + const expectedHeroes = [{name: 'A'}, {name: 'B'}] + const mockService = {getHeroes: () => expectedHeroes } it('should have heroes when HeroListComponent created', () => { - let hlc = new HeroListComponent(mockService); - expect(hlc.heroes.length).toEqual(expectedHeroes.length); + // Pass the mock to the constructor as the Angular injector would + const component = new HeroListComponent(mockService); + expect(component.heroes.length).toEqual(expectedHeroes.length); }); // #enddocregion spec diff --git a/aio/content/guide/dependency-injection-in-action.md b/aio/content/guide/dependency-injection-in-action.md index e974c3e6433c5..46a6cd11d5842 100644 --- a/aio/content/guide/dependency-injection-in-action.md +++ b/aio/content/guide/dependency-injection-in-action.md @@ -5,51 +5,6 @@ Dependency Injection is a powerful pattern for managing code dependencies. This cookbook explores many of the features of Dependency Injection (DI) in Angular. {@a toc} - - See the of the code in this cookbook. @@ -79,7 +34,7 @@ is all the registration you need. A *provider* is something that can create or deliver a service. Angular creates a service instance from a class provider by using `new`. -Read more about providers in the [Dependency Injection](guide/dependency-injection#injector-providers) +Read more about providers in the [Dependency Injection](guide/dependency-injection#register-providers-ngmodule) guide. diff --git a/aio/content/guide/dependency-injection-pattern.md b/aio/content/guide/dependency-injection-pattern.md new file mode 100644 index 0000000000000..d13f2baa3a1ef --- /dev/null +++ b/aio/content/guide/dependency-injection-pattern.md @@ -0,0 +1,167 @@ +# The Dependency Injection pattern + +**Dependency injection** is an important application design pattern. +It's used so widely that almost everyone just calls it _DI_. + +Angular has its own dependency injection framework, and +you really can't build an Angular application without it. + +This page covers what DI is and why it's useful. + +When you've learned the general pattern, you're ready to turn to +the [Angular Dependency Injection](guide/dependency-injection) guide to see how it works in an Angular app. + +{@a why-di } + +## Why dependency injection? + +To understand why dependency injection is so important, consider an example without it. +Imagine writing the following code: + + + + +The `Car` class creates everything it needs inside its constructor. +What's the problem? +The problem is that the `Car` class is brittle, inflexible, and hard to test. + +This `Car` needs an engine and tires. Instead of asking for them, +the `Car` constructor instantiates its own copies from +the very specific classes `Engine` and `Tires`. + +What if the `Engine` class evolves and its constructor requires a parameter? +That would break the `Car` class and it would stay broken until you rewrote it along the lines of +`this.engine = new Engine(theNewParameter)`. +The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`. +You may not anticipate them even now. +But you'll *have* to start caring because +when the definition of `Engine` changes, the `Car` class must change. +That makes `Car` brittle. + +What if you want to put a different brand of tires on your `Car`? Too bad. +You're locked into whatever brand the `Tires` class creates. That makes the +`Car` class inflexible. + +Right now each new car gets its own `engine`. It can't share an `engine` with other cars. +While that makes sense for an automobile engine, +surely you can think of other dependencies that should be shared, such as the onboard +wireless connection to the manufacturer's service center. This `Car` lacks the flexibility +to share services that have been created previously for other consumers. + +When you write tests for `Car` you're at the mercy of its hidden dependencies. +Is it even possible to create a new `Engine` in a test environment? +What does `Engine` depend upon? What does that dependency depend on? +Will a new instance of `Engine` make an asynchronous call to the server? +You certainly don't want that going on during tests. + +What if the `Car` should flash a warning signal when tire pressure is low? +How do you confirm that it actually does flash a warning +if you can't swap in low-pressure tires during the test? + +You have no control over the car's hidden dependencies. +When you can't control the dependencies, a class becomes difficult to test. + +How can you make `Car` more robust, flexible, and testable? + +{@a ctor-injection} +That's super easy. Change the `Car` constructor to a version with DI: + + + + + + + + + + + +See what happened? The definition of the dependencies are +now in the constructor. +The `Car` class no longer creates an `engine` or `tires`. +It just consumes them. + +
+ +This example leverages TypeScript's constructor syntax for declaring +parameters and properties simultaneously. + +
+ +Now you can create a car by passing the engine and tires to the constructor. + + + + +How cool is that? +The definition of the `engine` and `tire` dependencies are +decoupled from the `Car` class. +You can pass in any kind of `engine` or `tires` you like, as long as they +conform to the general API requirements of an `engine` or `tires`. + +Now, if someone extends the `Engine` class, that is not `Car`'s problem. + +
+ +The _consumer_ of `Car` has the problem. The consumer must update the car creation code to +something like this: + + + + + +The critical point is this: the `Car` class did not have to change. +You'll take care of the consumer's problem shortly. + +
+ +The `Car` class is much easier to test now because you are in complete control +of its dependencies. +You can pass mocks to the constructor that do exactly what you want them to do +during each test: + + + + +**You just learned what dependency injection is**. + +It's a coding pattern in which a class receives its dependencies from external +sources rather than creating them itself. + +Cool! But what about that poor consumer? +Anyone who wants a `Car` must now +create all three parts: the `Car`, `Engine`, and `Tires`. +The `Car` class shed its problems at the consumer's expense. +You need something that takes care of assembling these parts. + +You _could_ write a giant class to do that: + + + + +It's not so bad now with only three creation methods. +But maintaining it will be hairy as the application grows. +This factory is going to become a huge spiderweb of +interdependent factory methods! + +Wouldn't it be nice if you could simply list the things you want to build without +having to define which dependency gets injected into what? + +This is where the dependency injection framework comes into play. +Imagine the framework had something called an _injector_. +You register some classes with this injector, and it figures out how to create them. + +When you need a `Car`, you simply ask the injector to get it for you and you're good to go. + + + + +Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. +The consumer knows nothing about creating a `Car`. +You don't have a gigantic factory class to maintain. +Both `Car` and consumer simply ask for what they need and the injector delivers. + +This is what a **dependency injection framework** is all about. + +Now that you know what dependency injection is and appreciate its benefits, +turn to the [Angular Dependency Injection](guide/dependency-injection) guide to see how it is implemented in Angular. diff --git a/aio/content/guide/dependency-injection.md b/aio/content/guide/dependency-injection.md index 900f923a4730c..4731a2ffaff9a 100644 --- a/aio/content/guide/dependency-injection.md +++ b/aio/content/guide/dependency-injection.md @@ -1,510 +1,266 @@ -# Dependency Injection +# Angular Dependency Injection -**Dependency injection** is an important application design pattern. -Angular has its own dependency injection framework, and -you really can't build an Angular application without it. -It's used so widely that almost everyone just calls it _DI_. +**Dependency Injection (DI)** is a way to create objects that depend upon other objects. +A Dependency Injection system supplies the dependent objects (called the _dependencies_) +when it creates an instance of an object. -This page covers what DI is, why it's so useful, -and [how to use it](guide/dependency-injection#angular-di) in an Angular app. +The [Dependency Injection pattern](guide/dependency-injection-pattern) page describes this general approach. +_The guide you're reading now_ explains how Angular's own Dependency Injection system works. -Run the . +## DI by example -{@a why-di } - -## Why dependency injection? - -To understand why dependency injection is so important, consider an example without it. -Imagine writing the following code: - - - - - - - - -The `Car` class creates everything it needs inside its constructor. -What's the problem? -The problem is that the `Car` class is brittle, inflexible, and hard to test. - -This `Car` needs an engine and tires. Instead of asking for them, -the `Car` constructor instantiates its own copies from -the very specific classes `Engine` and `Tires`. - -What if the `Engine` class evolves and its constructor requires a parameter? -That would break the `Car` class and it would stay broken until you rewrote it along the lines of -`this.engine = new Engine(theNewParameter)`. -The `Engine` constructor parameters weren't even a consideration when you first wrote `Car`. -You may not anticipate them even now. -But you'll *have* to start caring because -when the definition of `Engine` changes, the `Car` class must change. -That makes `Car` brittle. - -What if you want to put a different brand of tires on your `Car`? Too bad. -You're locked into whatever brand the `Tires` class creates. That makes the -`Car` class inflexible. - -Right now each new car gets its own `engine`. It can't share an `engine` with other cars. -While that makes sense for an automobile engine, -surely you can think of other dependencies that should be shared, such as the onboard -wireless connection to the manufacturer's service center. This `Car` lacks the flexibility -to share services that have been created previously for other consumers. - -When you write tests for `Car` you're at the mercy of its hidden dependencies. -Is it even possible to create a new `Engine` in a test environment? -What does `Engine` depend upon? What does that dependency depend on? -Will a new instance of `Engine` make an asynchronous call to the server? -You certainly don't want that going on during tests. - -What if the `Car` should flash a warning signal when tire pressure is low? -How do you confirm that it actually does flash a warning -if you can't swap in low-pressure tires during the test? - -You have no control over the car's hidden dependencies. -When you can't control the dependencies, a class becomes difficult to test. - -How can you make `Car` more robust, flexible, and testable? - -{@a ctor-injection} -That's super easy. Change the `Car` constructor to a version with DI: - - - - - - - - - - - - - - - - -See what happened? The definition of the dependencies are -now in the constructor. -The `Car` class no longer creates an `engine` or `tires`. -It just consumes them. - - -
- - - -This example leverages TypeScript's constructor syntax for declaring -parameters and properties simultaneously. - - -
- - - -Now you can create a car by passing the engine and tires to the constructor. - - - - - - - - -How cool is that? -The definition of the `engine` and `tire` dependencies are -decoupled from the `Car` class. -You can pass in any kind of `engine` or `tires` you like, as long as they -conform to the general API requirements of an `engine` or `tires`. - -Now, if someone extends the `Engine` class, that is not `Car`'s problem. - - -
- - - -The _consumer_ of `Car` has the problem. The consumer must update the car creation code to -something like this: - - - - - - - - -The critical point is this: the `Car` class did not have to change. -You'll take care of the consumer's problem shortly. - - -
- - - -The `Car` class is much easier to test now because you are in complete control -of its dependencies. -You can pass mocks to the constructor that do exactly what you want them to do -during each test: - - - - - - - - -**You just learned what dependency injection is**. - -It's a coding pattern in which a class receives its dependencies from external -sources rather than creating them itself. - -Cool! But what about that poor consumer? -Anyone who wants a `Car` must now -create all three parts: the `Car`, `Engine`, and `Tires`. -The `Car` class shed its problems at the consumer's expense. -You need something that takes care of assembling these parts. - -You _could_ write a giant class to do that: - - - - - - - - -It's not so bad now with only three creation methods. -But maintaining it will be hairy as the application grows. -This factory is going to become a huge spiderweb of -interdependent factory methods! - -Wouldn't it be nice if you could simply list the things you want to build without -having to define which dependency gets injected into what? - -This is where the dependency injection framework comes into play. -Imagine the framework had something called an _injector_. -You register some classes with this injector, and it figures out how to create them. - -When you need a `Car`, you simply ask the injector to get it for you and you're good to go. - - - - - - - - -Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. -The consumer knows nothing about creating a `Car`. -You don't have a gigantic factory class to maintain. -Both `Car` and consumer simply ask for what they need and the injector delivers. - -This is what a **dependency injection framework** is all about. - -Now that you know what dependency injection is and appreciate its benefits, -read on to see how it is implemented in Angular. - -{@a angular-di} - -## Angular dependency injection - -Angular ships with its own dependency injection framework. This framework can also be used -as a standalone module by other applications and frameworks. - -To see what it can do when building components in Angular, -start with a simplified version of the `HeroesComponent` -that from the [The Tour of Heroes](tutorial/). +You'll learn Angular Dependency Injection through a discussion of the sample app that accompanies this guide. +Run the anytime. +Start by reviewing this simplified version of the _heroes_ feature +from the [The Tour of Heroes](tutorial/). - - - + - - - +The `HeroesComponent` is the top-level heroes component. +It's only purpose is to display the `HeroListComponent` +which displays a list of hero names. +This version of the `HeroListComponent` gets its `heroes` from the `HEROES` array, an in-memory collection +defined in a separate `mock-heroes` file. -The `HeroesComponent` is the root component of the *Heroes* feature area. -It governs all the child components of this area. -This stripped down version has only one child, `HeroListComponent`, -which displays a list of heroes. - + + -Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection -defined in another file. That may suffice in the early stages of development, but it's far from ideal. -As soon as you try to test this component or want to get your heroes data from a remote server, -you'll have to change the implementation of `heroes` and -fix every other use of the `HEROES` mock data. - -It's better to make a service that hides how the app gets hero data. - - -
+As soon as you try to test this component or get heroes from a remote server, +you'll have to change the implementation of `HerosListComponent` and +replace every other use of the `HEROES` mock data. +It's better to hide these details inside a _service_ class, +[defined in its own file](#one-class-per-file). +## Create an injectable _HeroService_ -Given that the service is a -[separate concern](https://en.wikipedia.org/wiki/Separation_of_concerns), -consider writing the service code in its own file. - -See [this note](guide/dependency-injection#one-class-per-file) for details. - -
+The [**Angular CLI**](https://cli.angular.io/) can generate a new `HeroService` class in the `src/app/heroes` folder with this command. + +ng generate service heroes/hero + +That command creates the following `HeroService` skeleton. -The following `HeroService` exposes a `getHeroes` method that returns -the same mock data as before, but none of its consumers need to know that. + + +Assume for now that the [`@Injectable` decorator](#injectable) is an essential ingredient in every Angular service definition. +The rest of the class has been rewritten to expose a `getHeroes` method +that returns the same mock data as before. - +Of course, this isn't a real data service. +If the app were actually getting data from a remote server, +the `getHeroes` method signature would have to be asynchronous. +That's a defect we can safely ignore in this guide where our focus is on +_injecting the service_ into the `HeroList` component. +{@a injector-config} +{@a bootstrap} -
- - +## Register a service provider -The `@Injectable()` decorator above the service class is -covered [shortly](guide/dependency-injection#injectable). +A _service_ is just a class in Angular until you register it with an Angular dependency injector. +An Angular injector is responsible for creating service instances and injecting them into classes like the `HeroListComponent`. -
+You rarely create an Angular injector yourself. +Angular creates injectors for you as it executes the app, +starting with the _root injector_ that it creates during the [bootstrap process](guide/bootstrapping). +You do have to register _providers_ with an injector +before the injector can create that service. +**Providers** tell the injector _how to create the service_. +Without a provider, the injector would not know +that it is responsible for injecting the service +nor be able to create the service.
- - -Of course, this isn't a real service. -If the app were actually getting data from a remote server, the API would have to be -asynchronous, perhaps returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). -You'd also have to rewrite the way components consume the service. -This is important in general, but not in this example. - - -
- - - -A service is nothing more than a class in Angular. -It remains nothing more than a class until you register it with an Angular injector. - - -
+You'll learn much more about _providers_ [below](#providers). +For now it is sufficient to know that they create services +and must be registered with an injector.
+You can register a provider with any Angular decorator that supports the **`providers` array property**. +Many Angular decorators accept metadata with a `providers` property. +The two most important examples are `@Component` and `@NgModule`. -{@a injector-config} - - -### Configuring the injector - -You don't have to create an Angular injector. -Angular creates an application-wide injector for you during the bootstrap process. +{@a register-providers-component} +### _@Component_ providers - +Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array. + - - -You do have to configure the injector by registering the **providers** -that create the services the application requires. -This guide explains what [providers](guide/dependency-injection#providers) are later. - - -You can either register a provider within an [NgModule](guide/ngmodule) or in application components. - - {@a register-providers-ngmodule} +### _@NgModule_ providers -### Registering providers in an _NgModule_ -Here's the `AppModule` that registers two providers, `UserService` and an `APP_CONFIG` provider, -in its `providers` array. - - - +In the following excerpt, the root `AppModule` registers two providers in its `providers` array. + +The first entry registers the `UserService` class (_not shown_) under the `UserService` _injection token_. +The second registers a value (`HERO_DI_CONFIG`) under the `APP_CONFIG` _injection token_. +Thanks to these registrations, Angular can inject the `UserService` or the `HERO_DI_CONFIG` value +into any class that it creates. -Because the `HeroService` is used _only_ within the `HeroesComponent` -and its subcomponents, the top-level `HeroesComponent` is the ideal -place to register it. +
+You'll learn about _injection tokens_ and _provider_ syntax [below](#providers). +
-{@a register-providers-component} +{@a ngmodule-vs-comp} -### Registering providers in a component +### _@NgModule_ or _@Component_? -Here's a revised `HeroesComponent` that registers the `HeroService` in its `providers` array. +Should you register a service with an Angular module or with a component? +The two choices lead to differences in service _scope_ and service _lifetime_. +**Angular module providers** (`@NgModule.providers`) are registered with the application's root injector. +Angular can inject the corresponding services in any class it creates. +Once created, a service instance lives for the life of the app and Angular injects this one service instance in every class that needs it. - - - +You're likely to inject the `UserService` in many places throughout the app +and will want to inject the same service instance every time. +Providing the `UserService` with an Angular module is a good choice. +
+To be precise, Angular module providers are registered with the root injector +_unless the module is_ [lazy loaded](guide/ngmodule#lazy-load). +In this sample, all modules are _eagerly loaded_ when the application starts, +so all module providers are registered with the app's root injector. -{@a ngmodule-vs-comp} +

+
-### When to use _NgModule_ versus an application component +**A component's providers** (`@Component.providers`) are registered with each component instance's own injector. -On the one hand, a provider in an `NgModule` is registered in the root injector. That means that every provider -registered within an `NgModule` will be accessible in the _entire application_. +Angular can only inject the corresponding services in that component instance or one of its descendant component instances. +Angular cannot inject the same service instance anywhere else. -On the other hand, a provider registered in an application component is available only on -that component and all its children. +Note that a component-provided service may have a limited lifetime. Each new instance of the component gets its own instance of the service +and, when the component instance is destroyed, so is that service instance. -Here, the `APP_CONFIG` service needs to be available all across the application, so it's -registered in the `AppModule` `@NgModule` `providers` array. -But since the `HeroService` is only used within the *Heroes* -feature area and nowhere else, it makes sense to register it in -the `HeroesComponent`. +In this sample app, the `HeroComponent` is created when the application starts +and is never destroyed so the `HeroService` created for the `HeroComponent` also live for the life of the app. +If you want to restrict `HeroService` access to the `HeroComponent` and its nested `HeroListComponent`, +providing the `HeroService` in the `HeroComponent` may be a good choice.
- - -Also see *"Should I add app-wide providers to the root `AppModule` or -the root `AppComponent`?"* in the [NgModule FAQ](guide/ngmodule-faq#q-root-component-or-module). - +The scope and lifetime of component-provided services is a consequence of [the way Angular creates component instances](#component-child-injectors).
+## Inject a service +The `HeroListComponent` should get heroes from the `HeroService`. -{@a prep-for-injection} +The component shouldn't create the `HeroService` with `new`. +It should ask for the `HeroService` to be injected. +You can tell Angular to inject a dependency in the component's constructor by specifying a **constructor parameter with the dependency type**. +Here's the `HeroListComponent` constructor, asking for the `HeroService` to be injected. -### Preparing the _HeroListComponent_ for injection - -The `HeroListComponent` should get heroes from the injected `HeroService`. -Per the dependency injection pattern, the component must ask for the service in its -constructor, [as discussed earlier](guide/dependency-injection#ctor-injection). -It's a small change: + + +Of course, the `HeroListComponent` should do something with the injected `HeroService`. +Here's the revised component, making use of the injected service, side-by-side with the previous version for comparison. - - - + - - + - +Notice that the `HeroListComponent` doesn't know where the `HeroService` comes from. +_You_ know that it comes from the parent `HeroesComponent`. +But if you decided instead to provide the `HeroService` in the `AppModule`, +the `HeroListComponent` wouldn't change at all. +The _only thing that matters_ is that the `HeroService` is provided in some parent injector. +{@a singleton-services} -
- - - -#### Focus on the constructor - -Adding a parameter to the constructor isn't all that's happening here. - - - - - - - - -Note that the constructor parameter has the type `HeroService`, and that -the `HeroListComponent` class has an `@Component` decorator -(scroll up to confirm that fact). -Also recall that the parent component (`HeroesComponent`) -has `providers` information for `HeroService`. - -The constructor parameter type, the `@Component` decorator, -and the parent's `providers` information combine to tell the -Angular injector to inject an instance of -`HeroService` whenever it creates a new `HeroListComponent`. - - -
- - - -{@a di-metadata} - - -### Implicit injector creation - -You saw how to use an injector to create a new -`Car` earlier in this guide. -You _could_ create such an injector -explicitly: - - - - - +## Singleton services +Services are singletons _within the scope of an injector_. +There is at most one instance of a service in a given injector. +There is only one root injector and the `UserService` is registered with that injector. +Therefore, there can be just one `UserService` instance in the entire app +and every class that injects `UserService` get this service instance. -You won't find code like that in the Tour of Heroes or any of the other -documentation samples. -You *could* write code that [explicitly creates an injector](guide/dependency-injection#explicit-injector) if you *had* to, -but it's not always the best choice. -Angular takes care of creating and calling injectors -when it creates components for you—whether through HTML markup, as in ``, -or after navigating to a component with the [router](guide/router). -If you let Angular do its job, you'll enjoy the benefits of automated dependency injection. +However, Angular DI is a +[hierarchical injection system](guide/hierarchical-dependency-injection), +which means that nested injectors can create their own service instances. +Angular creates nested injectors all the time. +{@a component-child-injectors} -{@a singleton-services} +## Component child injectors +For example, when Angular creates a new instance of a component that has `@Component.providers`, +it also creates a new _child injector_ for that instance. -### Singleton services +Component injectors are independent of each other and +each of them creates its own instances of the component-provided services. -Dependencies are singletons within the scope of an injector. -In this guide's example, a single `HeroService` instance is shared among the -`HeroesComponent` and its `HeroListComponent` children. +When Angular destroys one of these component instance, it also destroys the +component's injector and that injector's service instances. -However, Angular DI is a hierarchical injection -system, which means that nested injectors can create their own service instances. -For more information, see [Hierarchical Injectors](guide/hierarchical-dependency-injection). +Thanks to [injector inheritance](guide/hierarchical-dependency-injection), +you can still inject application-wide services into these components. +A component's injector is a child of its parent component's injector, +and a descendent of its parent's parent's injector, and so on all the way back to the application's _root_ injector. +Angular can inject a service provided by any injector in that lineage. +For example, Angular could inject a `HeroListComponent` +with both the `HeroService` provided in `HeroComponent` +and the `UserService` provided in `AppModule`. {@a testing-the-component} - -### Testing the component +## Testing the component Earlier you saw that designing a class for dependency injection makes the class easier to test. Listing dependencies as constructor parameters may be all you need to test application parts effectively. @@ -512,234 +268,132 @@ Listing dependencies as constructor parameters may be all you need to test appli For example, you can create a new `HeroListComponent` with a mock service that you can manipulate under test: - - - -
- - -Learn more in [Testing](guide/testing). - +Learn more in the [Testing](guide/testing) guide.
- - {@a service-needs-service} - -### When the service needs a service +## When the service needs a service The `HeroService` is very simple. It doesn't have any dependencies of its own. - What if it had a dependency? What if it reported its activities through a logging service? You'd apply the same *constructor injection* pattern, adding a constructor that takes a `Logger` parameter. -Here is the revision compared to the original. - +Here is the revised `HeroService` that injects the `Logger`, side-by-side with the previous service for comparison. - - +The constructor asks for an injected instance of a `Logger` and stores it in a private field called `logger`. +The `getHeroes()` method logs a message when asked to fetch heroes. -The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`. -You call that property within the `getHeroes()` method when anyone asks for heroes. - - -{@a injectable} - - -### Why _@Injectable()_? - -**[@Injectable()](api/core/Injectable)** marks a class as available to an -injector for instantiation. Generally speaking, an injector reports an -error when trying to instantiate a class that is not marked as -`@Injectable()`. - - -
- - - -As it happens, you could have omitted `@Injectable()` from the first -version of `HeroService` because it had no injected parameters. -But you must have it now that the service has an injected dependency. -You need it because Angular requires constructor parameter metadata -in order to inject a `Logger`. - - -
- +{@a logger-service} +#### The dependent _Logger_ service -
+The sample app's `Logger` service is quite simple: + + +If the app didn't provide this `Logger`, +Angular would throw an exception when it looked for a `Logger` to inject +into the `HeroService`. -
- Suggestion: add @Injectable() to every service class -
+ + ERROR Error: No provider for Logger! + +Because a singleton logger service is useful everywhere, +it's provided in the root `AppModule`. + + -Consider adding `@Injectable()` to every service class, even those that don't have dependencies -and, therefore, do not technically require it. Here's why: -
    +{@a injectable} -
  • - Future proofing: No need to remember @Injectable() when you add a dependency later. -
  • +## _@Injectable()_ -
  • - Consistency: All services follow the same rules, and you don't have to wonder why a decorator is missing. -
  • +The **[@Injectable()](api/core/Injectable)** decorator identifies a service class +that _might_ require injected dependencies. -
+The `HeroService` must be annotated with `@Injectable()` because it requires an injected `Logger`. +
+Always write `@Injectable()` with parentheses, not just `@Injectable`.
+When Angular creates a class whose constructor has parameters, +it looks for type and injection metadata about those parameters +so that it can inject the right service. +If Angular can't find that parameter information, it throws an error. -Injectors are also responsible for instantiating components -like `HeroesComponent`. So why doesn't `HeroesComponent` have -`@Injectable()`? - -You *can* add it if you really want to. It isn't necessary because the -`HeroesComponent` is already marked with `@Component`, and this -decorator class (like `@Directive` and `@Pipe`, which you learn about later) -is a subtype of [@Injectable()](api/core/Injectable). It is in -fact `@Injectable()` decorators that -identify a class as a target for instantiation by an injector. - +Angular can only find the parameter information _if the class has a decorator of some kind_. +While _any_ decorator will do, +the `@Injectable()` decorator is the standard decorator for service classes.
+The decorator requirement is imposed by TypeScript. +TypeScript normally discards parameter type information when it _transpiles_ the code to JavaScript. +It preserves this information if the class has a decorator +and the `emitDecoratorMetadata` compiler option is set `true` +in TypeScript's `tsconfig.json` configuration file, . - -At runtime, injectors can read class metadata in the transpiled JavaScript code -and use the constructor parameter type information -to determine what things to inject. - -Not every JavaScript class has metadata. -The TypeScript compiler discards metadata by default. -If the `emitDecoratorMetadata` compiler option is true -(as it should be in the `tsconfig.json`), -the compiler adds the metadata to the generated JavaScript -for _every class with at least one decorator_. - -While any decorator will trigger this effect, mark the service class with the -[@Injectable()](api/core/Injectable) decorator -to make the intent clear. - - -
- - - -
- - - -
- Always include the parentheses -
- - - -Always write `@Injectable()`, not just `@Injectable`. -The application will fail mysteriously if you forget the parentheses. - +The CLI configures `tsconfig.json` with `emitDecoratorMetadata: true` +It's your job to put `@Injectable()` on your service classes.
+The `Logger` service is annotated with `@Injectable()` decorator too, +although it has no constructor and no dependencies. -{@a logger-service} - -## Creating and registering a logger service - -Inject a logger into `HeroService` in two steps: - -1. Create the logger service. -1. Register it with the application. - -The logger service is quite simple: - - - - - - - - -You're likely to need the same logger service everywhere in your application, -so put it in the project's `app` folder and -register it in the `providers` array of the application module, `AppModule`. - - - - - - - - -If you forget to register the logger, Angular throws an exception when it first looks for the logger: - - - EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger) - - - - - -That's Angular telling you that the dependency injector couldn't find the *provider* for the logger. -It needed that provider to create a `Logger` to inject into a new -`HeroService`, which it needed to -create and inject into a new `HeroListComponent`. - -The chain of creations started with the `Logger` provider. *Providers* are the subject of the next section. +In fact, _every_ Angular service class in this app is annotated with the `@Injectable()` decorator, whether or not it has a constructor and dependencies. +`@Injectable()` is a required coding style for services. {@a providers} -## Injector providers +## Providers -A provider *provides* the concrete, runtime version of a dependency value. +A service provider *provides* the concrete, runtime version of a dependency value. The injector relies on **providers** to create instances of the services -that the injector injects into components and other services. - -You must register a service *provider* with the injector, or it won't know how to create the service. +that the injector injects into components, directives, pipes, and other services. -Earlier you registered the `Logger` service in the `providers` array of the metadata for the `AppModule` like this: +You must register a service *provider* with an injector, or it won't know how to create the service. +The next few sections explain the many ways you can specify a provider. - - - - +Almost all of the accompanying code snippets are extracts from the sample app's `providers.component.ts` file. +### The class as its own provider There are many ways to *provide* something that looks and behaves like a `Logger`. The `Logger` class itself is an obvious and natural provider. + + + + But it's not the only way. You can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`. @@ -749,48 +403,29 @@ Any of these approaches might be a good choice under the right circumstances. What matters is that the injector has a provider to go to when it needs a `Logger`. +{@a provide} -
- -
- - - -### The *Provider* class and _provide_ object literal - +### The _provide_ object literal -You wrote the `providers` array like this: - - - +Here's the class-provider syntax again. + - - This is actually a shorthand expression for a provider registration using a _provider_ object literal with two properties: - - - + - - -The first is the [token](guide/dependency-injection#token) that serves as the key for both locating a dependency value +The `provide` property holds the [token](guide/dependency-injection#token) that serves as the key for both locating a dependency value and registering the provider. -The second is a provider definition object, +The second property is always a provider definition object, which you can think of as a *recipe* for creating the dependency value. There are many ways to create dependency values just as there are many ways to write a recipe. - -
- -
- - +{@a class-provider} ### Alternative class providers @@ -798,40 +433,27 @@ Occasionally you'll ask a different class to provide the service. The following code tells the injector to return a `BetterLogger` when something asks for the `Logger`. - - - + - - {@a class-provider-dependencies} - ### Class provider with dependencies + Maybe an `EvenBetterLogger` could display the user name in the log message. This logger gets the user from the injected `UserService`, which is also injected at the application level. - - - + - - Configure it like `BetterLogger`. - - - + - - {@a aliased-class-providers} - ### Aliased class providers Suppose an old component depends upon an `OldLogger` class. @@ -848,57 +470,34 @@ The `OldLogger` should be an alias for `NewLogger`. You certainly do not want two different `NewLogger` instances in your app. Unfortunately, that's what you get if you try to alias `OldLogger` to `NewLogger` with `useClass`. - - - + - - The solution: alias with the `useExisting` option. - - - - {@a value-provider} - ### Value providers - Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. - - - + - - Then you register a provider with the `useValue` option, which makes this object play the logger role. - - - - See more `useValue` examples in the [Non-class dependencies](guide/dependency-injection#non-class-dependencies) and [InjectionToken](guide/dependency-injection#injection-token) sections. - -
- -
- - +{@a factory-provider} ### Factory providers @@ -923,44 +522,29 @@ Unlike `EvenBetterLogger`, you can't inject the `UserService` into the `HeroServ The `HeroService` won't have direct access to the user information to decide who is authorized and who is not. - Instead, the `HeroService` constructor takes a boolean flag to control display of secret heroes. - - - - You can inject the `Logger`, but you can't inject the boolean `isAuthorized`. You'll have to take over the creation of new instances of this `HeroService` with a factory provider. A factory provider needs a factory function: - - - - Although the `HeroService` has no access to the `UserService`, the factory function does. You inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function: - - - -
- - The `useFactory` field tells Angular that the provider is a factory function whose implementation is the `heroServiceFactory`. @@ -968,11 +552,8 @@ The `deps` property is an array of [provider tokens](guide/dependency-injection# The `Logger` and `UserService` classes serve as tokens for their own class providers. The injector resolves these tokens and injects the corresponding services into the matching factory function parameters. -
- - Notice that you captured the factory provider in an exported variable, `heroServiceProvider`. This extra step makes the factory provider reusable. You can register the `HeroService` with this variable wherever you need it. @@ -981,20 +562,16 @@ In this sample, you need it only in the `HeroesComponent`, where it replaces the previous `HeroService` registration in the metadata `providers` array. Here you see the new and the old implementation side-by-side: - - - - + - {@a token} ## Dependency injection tokens @@ -1007,85 +584,54 @@ In all previous examples, the dependency value has been a class *instance*, and the class *type* served as its own lookup key. Here you get a `HeroService` directly from the injector by supplying the `HeroService` type as the token: - - - - You have similar good fortune when you write a constructor that requires an injected class-based dependency. When you define a constructor parameter with the `HeroService` class type, Angular knows to inject the service associated with that `HeroService` class token: - - - - This is especially convenient when you consider that most dependency values are provided by classes. - {@a non-class-dependencies} - ### Non-class dependencies -

- What if the dependency value isn't a class? Sometimes the thing you want to inject is a - string, function, or object. -

- - - -

- Applications often define configuration objects with lots of small facts - (like the title of the application or the address of a web API endpoint) - but these configuration objects aren't always instances of a class. - They can be object literals such as this one: -

- +What if the dependency value isn't a class? Sometimes the thing you want to inject is a +string, function, or object. +Applications often define configuration objects with lots of small facts +(like the title of the application or the address of a web API endpoint) +but these configuration objects aren't always instances of a class. +They can be object literals such as this one: - - + - - What if you'd like to make this configuration object available for injection? You know you can register an object with a [value provider](guide/dependency-injection#value-provider). - But what should you use as the token? You don't have a class to serve as a token. There is no `AppConfig` class. -
- - ### TypeScript interfaces aren't valid tokens -The `HERO_DI_CONFIG` constant has an interface, `AppConfig`. Unfortunately, you -cannot use a TypeScript interface as a token: - - +The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface. +Unfortunately, you cannot use a TypeScript interface as a token: + - - - - + - - That seems strange if you're used to dependency injection in strongly typed languages, where an interface is the preferred dependency lookup key. @@ -1093,70 +639,45 @@ It's not Angular's doing. An interface is a TypeScript design-time artifact. Jav The TypeScript interface disappears from the generated JavaScript. There is no interface type information left for Angular to find at runtime. -
- - {@a injection-token} - ### _InjectionToken_ One solution to choosing a provider token for non-class dependencies is to define and use an [*InjectionToken*](api/core/InjectionToken). The definition of such a token looks like this: - - - - The type parameter, while optional, conveys the dependency's type to developers and tooling. The token description is another developer aid. Register the dependency provider using the `InjectionToken` object: - - - + - - Now you can inject the configuration object into any constructor that needs it, with the help of an `@Inject` decorator: - - - -
- - Although the `AppConfig` interface plays no role in dependency injection, it supports typing of the configuration object within the class. -
- - Alternatively, you can provide and inject the configuration object in an ngModule like `AppModule`. - - -
- -
- + +{@a optional} ## Optional dependencies @@ -1165,25 +686,16 @@ a `logger`? You can tell Angular that the dependency is optional by annotating the constructor argument with `@Optional()`: - - - - - - - When using `@Optional()`, your code must be prepared for a null value. If you don't register a `logger` somewhere up the line, the injector will set the value of `logger` to null. - - ## Summary You learned the basics of Angular dependency injection in this page. @@ -1203,13 +715,9 @@ nested injectors, in Developers rarely work directly with an injector, but here's an `InjectorComponent` that does. - - - - An `Injector` is itself an injectable service. In this example, Angular injects the component's own `Injector` into the component's constructor. @@ -1245,7 +753,7 @@ must acquire services generically and dynamically. {@a one-class-per-file} -## Appendix: Why have one class per file +## Appendix: one class per file Having multiple classes in the same file is confusing and best avoided. Developers expect one class per file. Keep them happy. @@ -1259,12 +767,10 @@ you'll get a runtime null reference error.
- - You actually can define the component first with the help of the `forwardRef()` method as explained in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). -But why flirt with trouble? -Avoid the problem altogether by defining components and services in separate files. + +But it's best to avoid the problem altogether by defining components and services in separate files.
diff --git a/aio/content/navigation.json b/aio/content/navigation.json index df7203a0e408c..137de17ce5c41 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -243,10 +243,15 @@ "title": "Dependency Injection", "tooltip": "Dependency Injection: creating and injecting services", "children": [ + { + "url": "guide/dependency-injection-pattern", + "title": "The Dependency Injection pattern", + "tooltip": "Learn about the dependency injection pattern behind the Angular DI system." + }, { "url": "guide/dependency-injection", - "title": "Dependency Injection", - "tooltip": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"." + "title": "Angular Dependency Injection", + "tooltip": "Angular's dependency injection system creates and delivers dependent services to Angular-created classes." }, { "url": "guide/hierarchical-dependency-injection",