docs: Dependency Injection guides for CLI (#19892)

PR Close #19892
This commit is contained in:
Ward Bell 2017-10-23 17:44:49 -07:00 committed by Jason Aden
parent 5a2531ee45
commit c03186013c
25 changed files with 635 additions and 905 deletions

View File

@ -4,7 +4,8 @@
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
"!**/*.[0,1,2].*",
"**/dummy.module.ts"
],
"tags": ["dependency", "di"]
}

View File

@ -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;
}

View File

@ -1,7 +1,5 @@
// Early versions
// #docregion
import { Component } from '@angular/core';
import { Component } from '@angular/core';
@Component({
selector: 'app-root',

View File

@ -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

View File

@ -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';
<app-heroes id="authorized" *ngIf="isAuthorized"></app-heroes>
<app-heroes id="unauthorized" *ngIf="!isAuthorized"></app-heroes>
<app-providers></app-providers>
`,
providers: [Logger]
`
})
export class AppComponent {
title: string;

View File

@ -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<AppConfig>('app.config');
export const APP_CONFIG = new InjectionToken<AppConfig>('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'

View File

@ -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 { }

View File

@ -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 {}

View File

@ -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 {}

View File

@ -1,16 +1,17 @@
// #docregion
import { Component } from '@angular/core';
import { HEROES } from './mock-heroes';
@Component({
selector: 'app-hero-list',
template: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
`
})
// #docregion class
export class HeroListComponent {
heroes = HEROES;
}
// #enddocregion class

View File

@ -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: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
`
})
export class HeroListComponent {

View File

@ -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: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
({{hero.isSecret ? 'secret' : 'public'}})
</div>
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
({{hero.isSecret ? 'secret' : 'public'}})
</div>
`,
})
export class HeroListComponent {

View File

@ -0,0 +1,6 @@
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
constructor() { }
}

View File

@ -1,6 +1,5 @@
// #docregion
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable()

View File

@ -1,6 +1,5 @@
// #docregion
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';

View File

@ -1,6 +1,5 @@
// #docregion
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';

View File

@ -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: `
<h2>Heroes</h2>
<app-hero-list></app-hero-list>
<h2>Heroes</h2>
<app-hero-list></app-hero-list>
`
})
export class HeroesComponent { }

View File

@ -1,14 +1,13 @@
// #docregion
import { Component } from '@angular/core';
import { heroServiceProvider } from './hero.service.provider';
@Component({
selector: 'app-heroes',
providers: [ heroServiceProvider ],
template: `
<h2>Heroes</h2>
<app-hero-list></app-hero-list>
`,
providers: [heroServiceProvider]
<h2>Heroes</h2>
<app-hero-list></app-hero-list>
`
})
export class HeroesComponent { }

View File

@ -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: `

View File

@ -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 {}

View File

@ -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 = <HeroService> {getHeroes: () => expectedHeroes }
const expectedHeroes = [{name: 'A'}, {name: 'B'}]
const mockService = <HeroService> {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

View File

@ -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}
<!--
# Contents
* [Application-wide dependencies](guide/dependency-injection-in-action#app-wide-dependencies)
* [External module configuration](guide/dependency-injection-in-action#external-module-configuration)
* [`@Injectable()` and nested service dependencies](guide/dependency-injection-in-action#nested-dependencies)
* [`@Injectable()`](guide/dependency-injection-in-action#injectable-1)
* [Limit service scope to a component subtree](guide/dependency-injection-in-action#service-scope)
* [Multiple service instances (sandboxing)](guide/dependency-injection-in-action#multiple-service-instances)
* [Qualify dependency lookup with `@Optional()` and `@Host()`](guide/dependency-injection-in-action#qualify-dependency-lookup)
* [Demonstration](guide/dependency-injection-in-action#demonstration)
* [Inject the component's DOM element](guide/dependency-injection-in-action#component-element)
* [Define dependencies with providers](guide/dependency-injection-in-action#providers)
* [Defining providers](guide/dependency-injection-in-action#defining-providers)
* [The *provide* object literal](guide/dependency-injection-in-action#provide)
* [`useValue`&mdash;the *value provider*](guide/dependency-injection-in-action#usevalue)
* [`useClass`&mdash;the *class provider*](guide/dependency-injection-in-action#useclass)
* [`useExisting`&mdash;the *alias provider*](guide/dependency-injection-in-action#useexisting)
* [`useFactory`&mdash;the *factory provider*](guide/dependency-injection-in-action#usefactory)
* [Provider token alternatives: the class-interface and `InjectionToken`](guide/dependency-injection-in-action#tokens)
* [class-interface](guide/dependency-injection-in-action#class-interface)
* [`InjectionToken`](guide/dependency-injection-in-action#injection-token)
* [Inject into a derived class](guide/dependency-injection-in-action#di-inheritance)
* [Find a parent component by injection](guide/dependency-injection-in-action#find-parent)
* [Find parent with a known component type](guide/dependency-injection-in-action#known-parent)
* [Cannot find a parent by its base class](guide/dependency-injection-in-action#base-parent)
* [Find a parent by its class-interface](guide/dependency-injection-in-action#class-interface-parent)
* [Find a parent in a tree of parents with `@SkipSelf()`](guide/dependency-injection-in-action#parent-tree)
* [The `Parent` class-interface](guide/dependency-injection-in-action#parent-token)
* [A `provideParent()` helper function](guide/dependency-injection-in-action#provideparent)
* [Break circularities with a forward class reference (*forwardRef*)](guide/dependency-injection-in-action#forwardref)
-->
See the <live-example name="dependency-injection-in-action"></live-example>
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.
</div>

View File

@ -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:
<code-example path="dependency-injection/src/app/car/car-no-di.ts" region="car" title="src/app/car/car.ts (without DI)">
</code-example>
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:
<code-tabs>
<code-pane title="src/app/car/car.ts (excerpt with DI)" path="dependency-injection/src/app/car/car.ts" region="car-ctor">
</code-pane>
<code-pane title="src/app/car/car.ts (excerpt without DI)" path="dependency-injection/src/app/car/car-no-di.ts" region="car-ctor">
</code-pane>
</code-tabs>
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.
<div class="l-sub-section">
This example leverages TypeScript's constructor syntax for declaring
parameters and properties simultaneously.
</div>
Now you can create a car by passing the engine and tires to the constructor.
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation" linenums="false">
</code-example>
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.
<div class="l-sub-section">
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
something like this:
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-param" linenums="false">
</code-example>
The critical point is this: the `Car` class did not have to change.
You'll take care of the consumer's problem shortly.
</div>
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:
<code-example path="dependency-injection/src/app/car/car-creations.ts" region="car-ctor-instantiation-with-mocks" linenums="false">
</code-example>
**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:
<code-example path="dependency-injection/src/app/car/car-factory.ts" title="src/app/car/car-factory.ts">
</code-example>
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.
<code-example path="dependency-injection/src/app/car/car-injector.ts" region="injector-call" title="src/app/car/car-injector.ts" linenums="false">
</code-example>
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.

File diff suppressed because it is too large Load Diff

View File

@ -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",