docs: add DI cookbook

This commit is contained in:
Torgeir Helgevold 2016-03-26 12:18:13 -04:00 committed by Ward Bell
parent 56ed5e345c
commit c1f2c397ff
54 changed files with 2027 additions and 27 deletions

View File

@ -74,6 +74,7 @@ var _exampleBoilerplateFiles = [
'package.json',
'styles.css',
'tsconfig.json',
'tslint.json',
'typings.json'
];

View File

@ -6,5 +6,6 @@ package.json
karma.conf.js
karma-test-shim.js
tsconfig.json
tslint.json
npm-debug*.
**/protractor.config.js

View File

@ -12,8 +12,10 @@ import {Directive, ElementRef, Input} from 'angular2/core';
})
export class HighlightDirective {
// #docregion ctor
constructor(private el: ElementRef) { }
private _el:HTMLElement;
constructor(el: ElementRef) { this._el = el.nativeElement; }
// #enddocregion ctor
// #docregion mouse-methods
@ -21,7 +23,7 @@ export class HighlightDirective {
onMouseLeave() { this._highlight(null); }
private _highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
this._el.style.backgroundColor = color;
}
// #enddocregion mouse-methods

View File

@ -12,6 +12,9 @@ import {Directive, ElementRef, Input} from 'angular2/core';
// #docregion class-1
export class HighlightDirective {
private _defaultColor = 'red';
private _el:HTMLElement;
// #enddocregion class-1
// #enddocregion full
/*
@ -20,13 +23,7 @@ export class HighlightDirective {
// #enddocregion highlight
*/
// #docregion full
// #docregion class-1
// #docregion color
@Input('myHighlight') highlightColor: string;
// #enddocregion color
private _defaultColor = 'red';
// #enddocregion class-1
// #docregion defaultColor
@Input() set defaultColor(colorName:string){
this._defaultColor = colorName || this._defaultColor;
@ -34,7 +31,13 @@ export class HighlightDirective {
// #enddocregion defaultColor
// #docregion class-1
constructor(private el: ElementRef) { }
// #docregion color
@Input('myHighlight') highlightColor: string;
// #enddocregion color
// #enddocregion class-1
// #docregion class-1
constructor(el: ElementRef) { this._el = el.nativeElement; }
// #docregion mouse-enter
onMouseEnter() { this._highlight(this.highlightColor || this._defaultColor); }
@ -42,7 +45,7 @@ export class HighlightDirective {
onMouseLeave() { this._highlight(null); }
private _highlight(color:string) {
this.el.nativeElement.style.backgroundColor = color;
this._el.style.backgroundColor = color;
}
}
// #enddocregion class-1

View File

@ -0,0 +1,98 @@
describe('Dependency Injection Cookbook', function () {
beforeAll(function () {
browser.get('');
});
it('should render Logged in User example', function () {
var loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0);
expect(loggedInUser).toBeDefined();
});
it('"Bombasto" should be the logged in user', function () {
loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0);
expect(loggedInUser).toBeDefined();
});
it('should render sorted heroes', function () {
var sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0);
expect(sortedHeroes).toBeDefined();
});
it('Mr. Nice should be in sorted heroes', function () {
var sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Mr. Nice" and position()=2]')).get(0);
expect(sortedHero).toBeDefined();
});
it('RubberMan should be in sorted heroes', function () {
sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0);
expect(sortedHero).toBeDefined();
});
it('Magma should be in sorted heroes', function () {
sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0);
expect(sortedHero).toBeDefined();
});
it('should render Hero of the Month', function () {
var heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0);
expect(heroOfTheMonth).toBeDefined();
});
it('should render Hero Bios', function () {
var heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0);
expect(heroBios).toBeDefined();
});
it('should render Magma\'s description in Hero Bios', function () {
var magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0);
expect(magmaText).toBeDefined();
});
it('should render Magma\'s phone in Hero Bios and Contacts', function () {
var magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0);
expect(magmaPhone).toBeDefined();
});
it('should render Hero-of-the-Month runner-ups', function () {
var runnersUp = element(by.id('rups')).getText();
expect(runnersUp).toContain('RubberMan, Mr. Nice');
});
it('should render DateLogger log entry in Hero-of-the-Month', function () {
var logs = element.all(by.id('logs')).get(0).getText();
expect(logs).toContain('INFO: starting up at');
});
it('should highlight Hero Bios and Contacts container when mouseover', function () {
var target = element(by.css('div[myHighlight="yellow"]'))
var yellow = "rgba(255, 255, 0, 1)";
expect(target.getCssValue('background-color')).not.toEqual(yellow);
browser.actions().mouseMove(target).perform();
expect(target.getCssValue('background-color')).toEqual(yellow);
});
describe('in Parent Finder', function () {
var cathy1 = element(by.css('alex cathy'));
var craig1 = element(by.css('alex craig'));
var carol1 = element(by.css('alex carol p'));
var carol2 = element(by.css('barry carol p'));
it('"Cathy" should find "Alex" via the component class', function () {
expect(cathy1.getText()).toContain('Found Alex via the component');
});
it('"Craig" should not find "Alex" via the base class', function () {
expect(craig1.getText()).toContain('Did not find Alex via the base');
});
it('"Carol" within "Alex" should have "Alex" parent', function () {
expect(carol1.getText()).toContain('Alex');
});
it('"Carol" within "Barry" should have "Barry" parent', function () {
expect(carol2.getText()).toContain('Barry');
});
})
});

View File

@ -0,0 +1 @@
**/*.js

View File

@ -0,0 +1,39 @@
<h1>DI Cookbook</h1>
<div class="di-component">
<h3>Logged in user</h3>
<div>Name: {{userContext.name}}</div>
<div>Role: {{userContext.role}}</div>
</div>
<div class="di-component">
<h3>Hero Bios</h3>
<hero-bios></hero-bios>
</div>
<!-- #docregion highlight -->
<div id="highlight" class="di-component" myHighlight>
<h3>Hero Bios and Contacts</h3>
<div myHighlight="yellow">
<hero-bios-and-contacts></hero-bios-and-contacts>
</div>
</div>
<!-- #enddocregion highlight -->
<div class="di-component">
<hero-of-the-month></hero-of-the-month>
</div>
<div class="di-component">
<h3>Unsorted Heroes</h3>
<unsorted-heroes></unsorted-heroes>
</div>
<div class="di-component">
<h3>Sorted Heroes</h3>
<sorted-heroes></sorted-heroes>
</div>
<div class="di-component">
<parent-finder></parent-finder>
</div>

View File

@ -0,0 +1,46 @@
// #docregion
import { Component } from 'angular2/core';
import { HeroBiosComponent,
HeroBiosAndContactsComponent} from './hero-bios.component';
import { HeroOfTheMonthComponent } from './hero-of-the-month.component';
import { HeroesBaseComponent,
SortedHeroesComponent } from './sorted-heroes.component';
import { HighlightDirective } from './highlight.directive';
import { ParentFinderComponent } from './parent-finder.component';
const DIRECTIVES = [
HeroBiosComponent, HeroBiosAndContactsComponent,
HeroesBaseComponent, SortedHeroesComponent,
HeroOfTheMonthComponent,
HighlightDirective,
ParentFinderComponent
];
// #docregion import-services
import { LoggerService } from './logger.service';
import { UserContextService } from './user-context.service';
import { UserService } from './user.service';
@Component({
selector: 'my-app',
templateUrl:'app/app.component.html',
directives: DIRECTIVES,
// #docregion providers
providers: [LoggerService, UserContextService, UserService]
// #enddocregion providers
})
export class AppComponent {
// #enddocregion import-services
private userId:number = 1;
// #docregion ctor
constructor(logger:LoggerService, public userContext:UserContextService) {
userContext.loadUser(this.userId);
logger.logInfo('AppComponent initialized');
}
// #enddocregion ctor
// #docregion import-services
}
// #enddocregion import-services

View File

@ -0,0 +1,37 @@
/* tslint:disable:one-line:check-open-brace*/
// #docregion
import { Injectable } from 'angular2/core';
import { LoggerService } from './logger.service';
// #docregion minimal-logger
// class used as a restricting interface (hides other public members)
export abstract class MinimalLogger {
logInfo: (msg: string) => void;
logs: string[];
}
// #enddocregion minimal-logger
/*
// Transpiles to:
// #docregion minimal-logger-transpiled
var MinimalLogger = (function () {
function MinimalLogger() {}
return MinimalLogger;
}());
exports("MinimalLogger", MinimalLogger);
// #enddocregion minimal-logger-transpiled
*/
// #docregion date-logger-service
@Injectable()
// #docregion date-logger-service-signature
export class DateLoggerService extends LoggerService implements MinimalLogger
// #enddocregion date-logger-service-signature
{
logInfo(msg: any) { super.logInfo(stamp(msg)); }
logDebug(msg: any) { super.logInfo(stamp(msg)); }
logError(msg: any) { super.logError(stamp(msg)); }
}
function stamp(msg: any) { return msg + ' at ' + new Date(); }
// #enddocregion date-logger-service

View File

@ -0,0 +1,29 @@
// #docregion
import {Component, Input, OnInit} from 'angular2/core';
import {Hero} from './hero';
import {HeroCacheService} from './hero-cache.service';
// #docregion component
@Component({
selector:'hero-bio',
// #docregion template
template:`
<h4>{{hero.name}}</h4>
<ng-content></ng-content>
<textarea cols="25" [(ngModel)]="hero.description"></textarea>`,
// #enddocregion template
providers: [HeroCacheService]
})
export class HeroBioComponent implements OnInit {
@Input() heroId:number;
constructor(private _heroCache:HeroCacheService) { }
ngOnInit() { this._heroCache.fetchCachedHero(this.heroId); }
get hero() { return this._heroCache.hero; }
}
// #enddocregion component

View File

@ -0,0 +1,52 @@
// #docplaster
// #docregion
import { Component} from 'angular2/core';
import { HeroContactComponent } from './hero-contact.component';
import { HeroBioComponent } from './hero-bio.component';
import { HeroService } from './hero.service';
import { LoggerService } from './logger.service';
//////// HeroBiosComponent ////
// #docregion simple
@Component({
selector:'hero-bios',
template:`
<hero-bio [heroId]="1"></hero-bio>
<hero-bio [heroId]="2"></hero-bio>
<hero-bio [heroId]="3"></hero-bio>`,
directives:[HeroBioComponent],
providers: [HeroService]
})
export class HeroBiosComponent{
// #enddocregion simple
// #docregion ctor
constructor(logger: LoggerService) {
logger.logInfo('Creating HeroBiosComponent');
}
// #enddocregion ctor
// #docregion simple
}
// #enddocregion simple
//////// HeroBiosAndContactsComponent ////
// #docregion hero-bios-and-contacts
@Component({
selector:'hero-bios-and-contacts',
// #docregion template
template:`
<hero-bio [heroId]="1"> <hero-contact></hero-contact> </hero-bio>
<hero-bio [heroId]="2"> <hero-contact></hero-contact> </hero-bio>
<hero-bio [heroId]="3"> <hero-contact></hero-contact> </hero-bio>`,
// #enddocregion template
directives:[HeroBioComponent, HeroContactComponent],
// #docregion class-provider
providers: [HeroService]
// #enddocregion class-provider
})
export class HeroBiosAndContactsComponent{
constructor(logger: LoggerService) {
logger.logInfo('Creating HeroBiosAndContactsComponent');
}
}
// #enddocregion hero-bios-and-contacts

View File

@ -0,0 +1,19 @@
// #docregion
import {Injectable} from 'angular2/core';
import {Hero} from './hero';
import {HeroService} from './hero.service';
// #docregion service
@Injectable()
export class HeroCacheService {
hero:Hero;
constructor(private _heroService:HeroService){}
fetchCachedHero(id:number){
if (!this.hero) {
this.hero = this._heroService.getHeroById(id);
}
return this.hero
}
}
// #enddocregion service

View File

@ -0,0 +1,39 @@
// #docplaster
// #docregion
import {Component, ElementRef, Host, Inject, Optional} from 'angular2/core';
import {HeroCacheService} from './hero-cache.service';
import {LoggerService} from './logger.service';
// #docregion component
@Component({
selector:'hero-contact',
template:`
<div>Phone #: {{phoneNumber}}
<span *ngIf="hasLogger">!!!</span></div>`
})
export class HeroContactComponent {
hasLogger = false;
constructor(
// #docregion ctor-params
@Host() // limit to the host component's instance of the HeroCacheService
private _heroCache: HeroCacheService,
@Host() // limit search for logger; hides the application-wide logger
@Optional() // ok if the logger doesn't exist
private _loggerService: LoggerService
// #enddocregion ctor-params
) {
if (_loggerService) {
this.hasLogger = true;
_loggerService.logInfo('HeroContactComponent can log!');
}
// #docregion ctor
}
// #enddocregion ctor
get phoneNumber() { return this._heroCache.hero.phone; }
}
// #enddocregion component

View File

@ -0,0 +1,14 @@
// #docregion
import {Hero} from './hero';
export class HeroData {
createDb() {
let heroes = [
new Hero(1,"Windstorm"),
new Hero(2,"Bombasto"),
new Hero(3,"Magneta"),
new Hero(4,"Tornado")
];
return {heroes};
}
}

View File

@ -0,0 +1,75 @@
/* tslint:disable:one-line:check-open-brace*/
// #docplaster
// #docregion opaque-token
import {OpaqueToken} from 'angular2/core';
export const TITLE = new OpaqueToken('title');
// #enddocregion opaque-token
// #docregion hero-of-the-month
import { Component, Inject, provide } from 'angular2/core';
import { DateLoggerService,
MinimalLogger } from './date-logger.service';
import { Hero } from './hero';
import { HeroService } from './hero.service';
import { LoggerService } from './logger.service';
import { RUNNERS_UP,
runnersUpFactory } from './runners-up';
// #enddocregion hero-of-the-month
// #docregion some-hero
const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');
// #enddocregion some-hero
const template = `
<h3>{{title}}</h3>
<div>Winner: <strong>{{heroOfTheMonth.name}}</strong></div>
<div>Reason for award: <strong>{{heroOfTheMonth.description}}</strong></div>
<div>Runners-up: <strong id="rups">{{runnersUp}}</strong></div>
<p>Logs:</p>
<div id="logs">
<div *ngFor="#log of logs">{{log}}</div>
</div>
`;
// #docregion hero-of-the-month
@Component({
selector: 'hero-of-the-month',
template: template,
providers: [
// #docregion use-value
provide(Hero, {useValue: someHero}),
// #docregion provide-opaque-token
provide(TITLE, {useValue: 'Hero of the Month'}),
// #enddocregion provide-opaque-token
// #enddocregion use-value
// #docregion use-class
provide(HeroService, {useClass: HeroService}),
provide(LoggerService, {useClass: DateLoggerService}),
// #enddocregion use-class
// #docregion use-existing
provide(MinimalLogger, {useExisting: LoggerService}),
// #enddocregion use-existing
// #docregion provide-opaque-token, use-factory
provide(RUNNERS_UP, {useFactory: runnersUpFactory(2), deps: [Hero, HeroService]})
// #enddocregion provide-opaque-token, use-factory
]
})
export class HeroOfTheMonthComponent {
logs: string[] = [];
// #docregion ctor-signature
constructor(
logger: MinimalLogger,
public heroOfTheMonth: Hero,
@Inject(RUNNERS_UP) public runnersUp: string,
@Inject(TITLE) public title: string)
// #enddocregion ctor-signature
{
this.logs = logger.logs;
logger.logInfo('starting up');
}
}
// #enddocregion hero-of-the-month

View File

@ -0,0 +1,22 @@
// #docregion
import {Injectable} from 'angular2/core';
import {Hero} from './hero';
@Injectable()
export class HeroService {
//TODO move to database
private _heros:Array<Hero> = [
new Hero(1, 'RubberMan','Hero of many talents', '123-456-7899'),
new Hero(2, 'Magma','Hero of all trades', '555-555-5555'),
new Hero(3, 'Mr. Nice','The name says it all','111-222-3333')
];
getHeroById(id:number):Hero{
return this._heros.filter(hero => hero.id === id)[0];
}
getAllHeroes():Array<Hero>{
return this._heros;
}
}

View File

@ -0,0 +1,9 @@
// #docregion
export class Hero{
constructor(
public id: number,
public name:string,
public description?:string,
public phone?:string) {
}
}

View File

@ -0,0 +1,28 @@
// #docplaster
// #docregion
import {Directive, ElementRef, Input} from 'angular2/core';
@Directive({
selector: '[myHighlight]',
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()'
}
})
export class HighlightDirective {
@Input('myHighlight') highlightColor: string;
private _el: HTMLElement;
constructor(el: ElementRef) {
this._el = el.nativeElement;
}
onMouseEnter() { this._highlight(this.highlightColor || 'cyan'); }
onMouseLeave() { this._highlight(null); }
private _highlight(color: string) {
this._el.style.backgroundColor = color;
}
}

View File

@ -0,0 +1,16 @@
// #docregion
import {Injectable} from 'angular2/core';
@Injectable()
export class LoggerService {
logs: string[] = [];
logInfo(msg: any) { this.log(`INFO: ${msg}`); }
logDebug(msg: any) { this.log(`DEBUG: ${msg}`); }
logError(msg: any) { this.log(`ERROR: ${msg}`, true); }
private log(msg: any, isErr = false) {
this.logs.push(msg);
isErr ? console.error(msg) : console.log(msg);
}
}

View File

@ -0,0 +1,25 @@
// #docregion
import { bootstrap } from 'angular2/platform/browser';
import { provide } from 'angular2/core';
import { XHRBackend } from 'angular2/http';
import { LocationStrategy,
HashLocationStrategy,
ROUTER_PROVIDERS } from 'angular2/router';
import { HeroData } from './hero-data';
import { InMemoryBackendService,
SEED_DATA } from 'a2-in-memory-web-api/core';
import { AppComponent } from './app.component';
// #docregion bootstrap
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
provide(LocationStrategy,
{useClass: HashLocationStrategy}),
provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server
provide(SEED_DATA, { useClass: HeroData }) // in-mem server data
]).catch((err: any) => console.error(err));
// #enddocregion bootstrap

View File

@ -0,0 +1,227 @@
/* tslint:disable:no-unused-variable */
/* tslint:disable:one-line:check-open-brace*/
// #docplaster
// #docregion
import { Component, forwardRef, Optional, provide, SkipSelf } from 'angular2/core';
// A component base class (see AlexComponent)
export abstract class Base { name = 'Count Basie'; }
// Marker class, used as an interface
// #docregion parent
export abstract class Parent { name: string; }
// #enddocregion parent
const DifferentParent = Parent;
// #docregion provide-parent, provide-the-parent
// Helper method to provide the current component instance in the name of a `parentType`.
// #enddocregion provide-the-parent
// The `parentType` defaults to `Parent` when omitting the second parameter.
// #docregion provide-the-parent
const provideParent =
// #enddocregion provide-parent, provide-the-parent
// #docregion provide-parent
(component: any, parentType?: any) =>
provide(parentType || Parent, { useExisting: forwardRef(() => component) });
// #enddocregion provide-parent
// Simpler syntax version that always provides the component in the name of `Parent`.
const provideTheParent =
// #docregion provide-the-parent
(component: any) => provide(Parent, { useExisting: forwardRef(() => component) });
// #enddocregion provide-the-parent
///////// C - Child //////////
// #docregion carol
const templateC = `
<div class="c">
<h3>{{name}}</h3>
<p>My parent is {{parent?.name}}</p>
</div>`;
@Component({
selector: 'carol',
template: templateC
})
// #docregion carol-class
export class CarolComponent {
name= 'Carol';
// #docregion carol-ctor
constructor( @Optional() public parent: Parent ) { }
// #enddocregion carol-ctor
}
// #enddocregion carol-class
// #enddocregion carol
@Component({
selector: 'chris',
template: templateC
})
export class ChrisComponent {
name= 'Chris';
constructor( @Optional() public parent: Parent ) { }
}
////// Craig ///////////
/**
* Show we cannot inject a parent by its base class.
*/
// #docregion craig
@Component({
selector: 'craig',
template: `
<div class="c">
<h3>Craig</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the base class.
</div>`
})
export class CraigComponent {
constructor( @Optional() public alex: Base ) { }
}
// #enddocregion craig
// #docregion C_DIRECTIVES
const C_DIRECTIVES = [
CarolComponent, ChrisComponent, CraigComponent,
forwardRef(() => CathyComponent)
];
// #enddocregion C_DIRECTIVES
//////// B - Parent /////////
// #docregion barry
const templateB = `
<div class="b">
<div>
<h3>{{name}}</h3>
<p>My parent is {{parent?.name}}</p>
</div>
<carol></carol>
<chris></chris>
</div>`;
@Component({
selector: 'barry',
template: templateB,
directives: C_DIRECTIVES,
providers: [ provide(Parent, { useExisting: forwardRef(() => BarryComponent) }) ]
})
export class BarryComponent implements Parent {
name = 'Barry';
// #docregion barry-ctor
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
// #enddocregion barry-ctor
}
// #enddocregion barry
@Component({
selector: 'bob',
template: templateB,
directives: C_DIRECTIVES,
providers: [ provideParent(BobComponent) ]
})
export class BobComponent implements Parent {
name= 'Bob';
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
}
@Component({
selector: 'beth',
template: templateB,
directives: C_DIRECTIVES,
// #docregion beth-providers
providers: [ provideParent(BethComponent, DifferentParent) ]
// #enddocregion beth-providers
})
export class BethComponent implements Parent {
name= 'Beth';
constructor( @SkipSelf() @Optional() public parent: Parent ) { }
}
const B_DIRECTIVES = [ BarryComponent, BethComponent, BobComponent ];
///////// A - Grandparent //////
// #docregion alex, alex-1
@Component({
selector: 'alex',
template: `
<div class="a">
<h3>{{name}}</h3>
<cathy></cathy>
<craig></craig>
<carol></carol>
</div>`,
// #enddocregion alex-1
// #docregion alex-providers
providers: [ provide(Parent, { useExisting: forwardRef(() => AlexComponent) }) ],
// #enddocregion alex-providers
// #docregion alex-1
directives: C_DIRECTIVES
})
// #enddocregion alex-1
// Todo: Add `... implements Parent` to class signature
// #docregion alex-1
// #docregion alex-class-signature
export class AlexComponent extends Base
// #enddocregion alex-class-signature
{
name= 'Alex';
}
// #enddocregion alex, alex-1
/////
// #docregion alice
@Component({
selector: 'alice',
template: `
<div class="a">
<h3>{{name}}</h3>
<barry></barry>
<beth></beth>
<bob></bob>
<carol></carol>
</div> `,
directives: [ B_DIRECTIVES, C_DIRECTIVES ],
// #docregion alice-providers
providers: [ provideParent(AliceComponent) ]
// #enddocregion alice-providers
})
// #docregion alice-class-signature
export class AliceComponent implements Parent
// #enddocregion alice-class-signature
{
name= 'Alice';
}
// #enddocregion alice
////// Cathy ///////////
/**
* Show we can inject a parent by component type
*/
// #docregion cathy
@Component({
selector: 'cathy',
template: `
<div class="c">
<h3>Cathy</h3>
{{alex ? 'Found' : 'Did not find'}} Alex via the component class.<br>
</div>`
})
export class CathyComponent {
constructor( @Optional() public alex: AlexComponent ) { }
}
// #enddocregion cathy
///////// ParentFinder //////
@Component({
selector: 'parent-finder',
template: `
<h2>Parent Finder</h2>
<alex></alex>
<alice></alice>`,
directives: [ AlexComponent, AliceComponent ]
})
export class ParentFinderComponent { }

View File

@ -0,0 +1,25 @@
// #docplaster
// #docregion
import {OpaqueToken} from 'angular2/core';
import {Hero} from './hero';
import {HeroService} from './hero.service';
// #docregion runners-up
export const RUNNERS_UP = new OpaqueToken('RunnersUp');
// #docregion runners-up
// #docregion factory-synopsis
export function runnersUpFactory(take: number) {
return (winner: Hero, heroService: HeroService): string => {
/* ... */
// #enddocregion factory-synopsis
return heroService
.getAllHeroes()
.filter((hero) => hero.name !== winner.name)
.map(hero => hero.name)
.slice(0, Math.max(0, take))
.join(', ');
// #docregion factory-synopsis
};
};
// #enddocregion factory-synopsis

View File

@ -0,0 +1,51 @@
// #docplaster
// #docregion
import {Component, OnInit} from 'angular2/core';
import {Hero} from './hero';
import {HeroService} from './hero.service';
/////// HeroesBaseComponent /////
// #docregion heroes-base, injection
@Component({
selector: 'unsorted-heroes',
template: `<div *ngFor="#hero of heroes">{{hero.name}}</div>`,
providers: [HeroService]
})
export class HeroesBaseComponent implements OnInit {
constructor(private _heroService: HeroService) { }
// #enddocregion injection
heroes: Array<Hero>;
ngOnInit() {
this.heroes = this._heroService.getAllHeroes();
this._afterGetHeroes();
}
// Post-process heroes in derived class override.
protected _afterGetHeroes() {}
// #docregion injection
}
// #enddocregion heroes-base,injection
/////// SortedHeroesComponent /////
// #docregion sorted-heroes
@Component({
selector: 'sorted-heroes',
template: `<div *ngFor="#hero of heroes">{{hero.name}}</div>`,
providers: [HeroService]
})
export class SortedHeroesComponent extends HeroesBaseComponent {
constructor(heroService: HeroService) {
super(heroService);
}
protected _afterGetHeroes() {
this.heroes = this.heroes.sort((h1, h2) => {
return h1.name < h2.name ? -1 :
(h1.name > h2.name ? 1 : 0);
});
}
}
// #enddocregion sorted-heroes

View File

@ -0,0 +1,32 @@
// #docplaster
// #docregion
import {Injectable} from 'angular2/core';
import {LoggerService} from './logger.service';
import {UserService} from './user.service';
// #docregion injectables, injectable
@Injectable()
export class UserContextService {
// #enddocregion injectables, injectable
name:string;
role:string;
loggedInSince:Date;
// #docregion ctor, injectables
constructor(private _userService:UserService, private _loggerService:LoggerService){
// #enddocregion ctor, injectables
this.loggedInSince = new Date();
// #docregion ctor, injectables
}
// #enddocregion ctor, injectables
loadUser(userId:number){
let user = this._userService.getUserById(userId);
this.name = user.name;
this.role = user.role;
this._loggerService.logDebug('loaded User');
}
// #docregion injectables, injectable
}
// #enddocregion injectables, injectable

View File

@ -0,0 +1,10 @@
// #docregion
import {Injectable} from 'angular2/core';
@Injectable()
export class UserService {
getUserById(userId:number):any{
return {name:'Bombasto',role:'Admin'};
}
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Dependency Injection</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion style -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="sample.css">
<!-- #enddocregion style -->
<!-- IE required polyfills, in this exact order -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/rxjs/bundles/Rx.js"></script>
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<!-- Additional modules: router, http, in-mem-web-api -->
<script src="node_modules/angular2/bundles/router.dev.js"></script>
<script src="node_modules/angular2/bundles/http.dev.js"></script>
<script src="node_modules/a2-in-memory-web-api/web-api.js"></script>
<script>
System.config({
packages: {
app: {
format: 'register',
defaultExtension: 'js'
}
}
});
System.import('app/main')
.then(null, console.error.bind(console));
</script>
</head>
<body>
<my-app>Loading app...</my-app>
</body>
</html>

View File

@ -0,0 +1,9 @@
{
"description": "Dependency Injection",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}

View File

@ -0,0 +1,26 @@
.di-component{
padding: 10px;
width:300px;
margin-bottom: 10px;
}
div[myHighlight] {
padding: 2px 8px;
}
/* Parent Finder */
.a, .b, .c {
margin: 6px 2px 6px;
padding: 4px 6px;
}
.a {
border: solid 2px black;
}
.b {
background: lightblue;
border: solid 1px darkblue;
display: flex;
}
.c {
background: pink;
border: solid 1px red;
}

View File

@ -4,7 +4,7 @@
"description": "Master package.json, the superset of all dependencies for all of the _example package.json files.",
"main": "index.js",
"scripts": {
"start": "concurrently \"npm run tsc:w\" \"npm run lite\" ",
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"tsc": "tsc",
"tsc:w": "tsc -w",
"lite": "lite-server",

View File

@ -2,7 +2,7 @@
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "concurrently \"npm run tsc:w\" \"npm run lite\" ",
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"tsc": "tsc",
"tsc:w": "tsc -w",
"lite": "lite-server",

View File

@ -0,0 +1,93 @@
{
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-unreachable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@ -17,7 +17,13 @@
"intro": "Share information between different directives and components"
},
"dynamic-form": {
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Techniques for Dependency Injection",
"hide": true
},
"dynamic-forms": {
"title": "Dynamic Form",
"intro": "Render dynamic forms with NgFormModel",
"hide": true

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -16,10 +16,14 @@
"intro": "Share information between different directives and components"
},
"dynamic-form": {
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Techniques for Dependency Injection"
},
"dynamic-forms": {
"title": "Dynamic Form",
"intro": "Render dynamic forms with NgFormModel",
"hide": true
"intro": "Render dynamic forms with NgFormModel"
},
"ts-to-js": {

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -16,6 +16,11 @@
"intro": "Share information between different directives and components"
},
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Techniques for Dependency Injection"
},
"dynamic-form": {
"title": "Dynamic Form",
"intro": "Render dynamic forms with NgFormModel"

View File

@ -0,0 +1,908 @@
include ../_util-fns
:marked
Dependency Injection is a powerful pattern for managing code dependencies.
In this cookbook we will explore many of the features of Dependency Injection (DI) in Angular.
<a id="toc"></a>
:marked
## Table of contents
[Application-wide dependencies](#app-wide-dependencies)
[External module configuration](#external-module-configuration)
[*@Injectable* and nested service dependencies](#nested-dependencies)
[Limit service scope to a component subtree](#service-scope)
[Multiple service instances (sandboxing)](#multiple-service-instances)
[Qualify dependency lookup with *@Optional* and *@Host*](#qualify-dependency-lookup)
[Inject the component's DOM element](#component-element)
[Define dependencies with providers](#providers)
* [The *provide* function](#provide)
* [useValue - the *value provider*](#usevalue)
* [useClass - the *class provider*](#useclass)
* [useExisting - the *alias provider*](#useexisting)
* [useFactory - the *factory provider*](#usefactory)
[Provider token alternatives](#tokens)
* [class-interface](#class-interface)
* [OpaqueToken](#opaque-token)
[Inject into a derived class](#di-inheritance)
[Find a parent component by injection](#find-parent)
* [Find parent with a known component type](#known-parent)
* [Cannot find a parent by its base class](#base-parent)
* [Find a parent by its class-interface](#class-interface-parent)
* [Find a parent in a tree of parents (*@SkipSelf*)](#parent-tree)
* [A *provideParent* helper function](#provideparent)
[Break circularities with a forward class reference (*forwardRef*)](#forwardref)
:marked
**See the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**
of the code supporting this cookbook.
.l-main-section
<a id="app-wide-dependencies"></a>
:marked
## Application-wide dependencies
Register providers for dependencies used throughout the application in the root application component, `AppComponent`.
In the following example, we import and register several services
(the `LoggerService`, `UserContext`, and the `UserService`)
in the `@Component` metadata `providers` array.
+makeExample('cb-dependency-injection/ts/app/app.component.ts','import-services','app/app.component.ts (excerpt)')(format='.')
:marked
All of these services are implemented as classes.
Service classes can act as their own providers which is why listing them in the `providers` array
is all the registration we need.
.l-sub-section
:marked
A *provider* is something that can create or deliver a service.
Angular creates a service instance from a class provider by "new-ing" it.
Learn more about providers [below](#providers).
:marked
Now that we've registered these services,
Angular can inject them into the constructor of *any* component or service, *anywhere* in the application.
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','ctor','app/hero-bios.component.ts (component constructor injection)')(format='.')
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','ctor','app/user-context.service.ts (service constructor injection)')(format='.')
<a id="external-module-configuration"></a>
.l-main-section
:marked
## External module configuration
We can register _certain_ module providers when bootstrapping rather than in the root application component.
We'd do this when we expect to select or configure external modules that support our application
but (a) aren't conceptually part of the application and (b) that we could change later without
altering the essential logic of the application.
For example, we might configure the Component Router with different
[location strategies](../guide/router.html#location-strategy) based on environmental factors.
The choice of location strategy doesn't matter to the application itself.
We could sneak in a fake HTTP backend with sample data during development rather than
allow http calls to a remote server (that might not yet exist).
We'll switch to the real backend in production.
The application shouldn't know or care one way or the other.
See both examples in the following `main.ts`
where we list their service providers in an array in the second parameter of the `bootstrap` method.
+makeExample('cb-dependency-injection/ts/app/main.ts','bootstrap','app/main.ts')(format='.')
a(id="injectable")
a(id="nested-dependencies")
.l-main-section
:marked
## *@Injectable* and nested service dependencies
The consumer of an injected service does not know how to create that service.
It shouldn't care.
It's the dependency injection's job to create and cache that service.
Sometimes a service depends on other services ... which may depend on yet other services.
Resolving these nested dependencies in the correct order is also the framework's job.
At each step, the consumer of dependencies simply declares what it requires in its constructor and the framework takes over.
For example, we inject both the `LoggerService` and the `UserContext` in the `AppComponent`.
+makeExample('cb-dependency-injection/ts/app/app.component.ts','ctor','app/app.component.ts')(format='.')
:marked
The `UserContext` in turn has dependencies on both the `LoggerService` (again) and
a `UserService` that gathers information about a particular user.
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','injectables','user-context.service.ts (injection)')(format='.')
:marked
When Angular creates an`AppComponent`, the dependency injection framework creates an instance of the `LoggerService` and
starts to create the `UserContextService`.
The `UserContextService` needs the `LoggerService`, which the framework already has, and the `UserService`, which it has yet to create.
The `UserService` has no dependencies so the dependency injection framework can just `new` one into existence.
The beauty of dependency injection is that the author of `AppComponent` didn't care about any of this.
The author simply declared what was needed in the constructor (`LoggerService` and `UserContextService`) and the framework did the rest.
Once all the dependencies are in place, the `AppComponent` displays the user information:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User")
:marked
### *@Injectable()*
Notice the `@Injectable()`decorator on the `UserContextService` class.
+makeExample('cb-dependency-injection/ts/app/user-context.service.ts','injectable','user-context.service.ts (@Injectable)')(format='.')
:marked
That decorator makes it possible for Angular to identify the types of its two dependencies, `LoggerService` and `UserService`.
Technically, the `@Injectable()`decorator is only _required_ for a service class that has _its own dependencies_.
The `LoggerService` doesn't depend on anything. The logger would work if we omitted `@Injectable()`
and the generated code would be slightly smaller.
But the service would break the moment we gave it a dependency and we'd have to go back and
and add `@Injectable()` to fix it. We add `@Injectable()` from the start for the sake of consistency and to avoid future pain.
.alert.is-helpful
:marked
Although we recommend applying `@Injectable` to all service classes, do not feel bound by it.
Some developers prefer to add it only where needed and that's a reasonable policy too.
.l-sub-section
:marked
The `AppComponent` class had two dependencies as well but no `@Injectable()`.
It didn't need `@Injectable()` because that component class has the `@Component` decorator.
In Angular with TypeScript, a *single* decorator &mdash; *any* decorator &mdash; is sufficient to identify dependency types.
<a id="service-scope"></a>
.l-main-section
:marked
## Limit service scope to a component subtree
All injected service dependencies are singletons meaning that,
for a given dependency injector ("injector"), there is only one instance of service.
But an Angular application has multiple dependency injectors, arranged in a tree hierarchy that parallels the component tree.
So a particular service can be *provided* (and created) at any component level and multiple times
if provided in multiple components.
By default, a service dependency provided in one component is visible to all of its child components and
Angular injects the same service instance into all child components that ask for that service.
Accordingly, dependencies provided in the root `AppComponent` can be injected into *any* component *anywhere* in the application.
That isn't always desireable.
Sometimes we want to restrict service availability to a particular region of the application.
We can limit the scope of an injected service to a *branch* of the application hierarchy
by providing that service *at the sub-root component for that branch*.
Here we provide the `HeroService` to the `HeroesBaseComponent` by listing it in the `providers` array:
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','injection','app/sorted-heroes.component.ts (HeroesBaseComponent excerpt)')
:marked
When Angular creates the `HeroesBaseComponent`, it also creates a new instance of `HeroService`
that is visible only to the component and its children (if any).
We could also provide the `HeroService` to a *different* component elsewhere in the application.
That would result in a *different* instance of the service, living in a *different* injector.
.l-sub-section
:marked
We examples of such scoped `HeroService` singletons appear throughout the accompanying sample code,
including the `HeroBiosComponent`, `HeroOfTheMonthComponent`, and `HeroesBaseComponent`.
Each of these components has its own `HeroService` instance managing its own independent collection of heroes.
.l-main-section
.alert.is-helpful
:marked
### Take a break!
This much Dependency Injection knowledge may be all that many Angular developers
ever need to build their applications. It doesn't always have to be more complicated.
<a id="multiple-service-instances"></a>
.l-main-section
:marked
## Multiple service instances (sandboxing)
Sometimes we want multiple instances of a service at *the same level of the component hierarchy*.
A good example is a service that holds state for its companion component instance.
We need a separate instance of the service for each component.
Each service has its own work-state, isolated from the service-and-state of a different component.
We call this *sandboxing* because each service and component instance has its own sandbox to play in.
<a id="hero-bios-component"></a>
Imagine a `HeroBiosComponent` that presents three instances of the `HeroBioComponent`.
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','simple','ap/hero-bios.component.ts')
:marked
Each `HeroBioComponent` can edit a single hero's biography.
A `HeroBioComponent` relies on a `HeroCacheService` to fetch, cache, and perform other persistence operations on that hero.
+makeExample('cb-dependency-injection/ts/app/hero-cache.service.ts','service','app/hero-cache.service.ts')
:marked
Clearly the three instances of the `HeroBioComponent` can't share the same `HeroCacheService`.
They'd be competing with each other to determine which hero to cache.
Each `HeroBioComponent` gets its *own* `HeroCacheService` instance
by listing the `HeroCacheService` in its metadata `providers` array.
+makeExample('cb-dependency-injection/ts/app/hero-bio.component.ts','component','app/hero-bio.component.ts')
:marked
The parent `HeroBiosComponent` binds a value to the `heroId`.
The `ngOnInit` pass that `id` to the service which fetches and caches the hero.
The getter for the `hero` property pulls the cached hero from the service.
And the template displays this data-bound property.
Find this example in [live code](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
and confirm that the three `HeroBioComponent` instances have their own cached hero data.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-bios.png" alt="Bios")
a(id="optional")
a(id="qualify-dependency-lookup")
.l-main-section
:marked
## Qualify dependency lookup with *@Optional* and *@Host*
We learned that dependencies can be registered at any level in the component hierarchy.
When a component requests a dependency, Angular starts with that component's injector and walks up the injector tree
until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.
We *want* this behavior most of the time.
But sometimes we need to limit the search and/or accommodate a missing dependency.
We can modify Angular's search behavior with the `@Host` and `@Optional` qualifying decorators,
used individually or together.
The `@Optional` decorator tells Angular to continue when it can't find the dependency.
Angular sets the injection parameter to `null` instead.
The `@Host` decorator stops the upward search at the *host component*.
The host component is typically the component requesting the dependency.
But when this component is projected into a *parent* component, that parent component becomes the host.
We look at this second, more interesting case in our next example.
### Demonstration
The `HeroBiosAndContactsComponent` is a revision of the `HeroBiosComponent` that we looked at [above](#hero-bios-component).
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','hero-bios-and-contacts','app/hero-bios.component.ts (HeroBiosAndContactsComponent)')
:marked
Focus on the template:
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','template')(format='.')
:marked
We've inserted a `<hero-contact>` element between the `<hero-bio>` tags.
Angular *projects* (*transcludes*) the corresponding `HeroContactComponent` into the `HeroBioComponent` view,
placing it in the `<ng-content>` slot of the `HeroBioComponent` template:
+makeExample('cb-dependency-injection/ts/app/hero-bio.component.ts','template','app/hero-bio.component.ts (template)')(format='.')
:marked
It looks like this, with the heroe's telephone number from `HeroContactComponent` projected above the hero description:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png" alt="bio and contact")
:marked
Here's the `HeroContactComponent` which demonstrates the qualifying decorators that we're talking about in this section:
+makeExample('cb-dependency-injection/ts/app/hero-contact.component.ts','component','app/hero-contact.component.ts')
:marked
Focus on the constructor parameters
+makeExample('cb-dependency-injection/ts/app/hero-contact.component.ts','ctor-params','app/hero-contact.component.ts')(format='.')
:marked
The `@Host()` function decorating the `_heroCache` property ensures that
we get a reference to the cache service from the parent `HeroBioComponent`.
Angular throws if the parent lacks that service, even if a component higher in the component tree happens to have that service.
A second `@Host()` function decorates the `_loggerService` property.
We know the only `LoggerService` instance in the app is provided at the `AppComponent` level.
The host `HeroBioComponent` doesn't have its own `LoggerService` provider.
Angular would throw an error if we hadn't also decorated the property with the `@Optional()` function.
Thanks to `@Optional()`, Angular sets the `loggerService` to null and the rest of the component adapts.
.l-sub-section
:marked
We'll come back to the `elementRef` property shortly.
:marked
Here's the `HeroBiosAndContactsComponent` in action.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png" alt="Bios with contact into")
:marked
If we comment out the `@Host()` decorator, Angular now walks up the injector ancestor tree
until it finds the logger at the `AppComponent` level. The logger logic kicks in and the hero display updates
with the gratuituous "!!!", indicating that the logger was found.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png" alt="Without @Host")
:marked
On the other hand, if we restore the `@Host()` decorator and comment out `@Optional`,
the application fails for lack of the required logger at the host component level.
<br>
`EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)`
<a id="component-element"></a>
:marked
## Inject the component's element
On occasion we might need to access a component's corresponding DOM element.
Although we strive to avoid it, many visual effects and 3rd party tools (such as jQuery)
require DOM access.
To illustrate, we've written a simplified version of the `HighlightDirective` from
the [Attribute Directives](../guide/attribute-directives.html) chapter.
+makeExample('cb-dependency-injection/ts/app/highlight.directive.ts','','app/highlight.directive.ts')
:marked
The directive sets the background to a highlight color when the user mouses over the
DOM element to which it is applied.
Angular set the constructor's `el` parameter to the injected `ElementRef` which is
a wrapper around that DOM element.
Its `nativeElement` property exposes the DOM element for the directive to manipulate.
The sample code applies the directive's `myHighlight` attribute to two `<div>` tags,
first without a value (yielding the default color) and then with an assigned color value.
+makeExample('cb-dependency-injection/ts/app/app.component.html','highlight','app/app.component.html (highlight)')(format='.')
:marked
The following image shows the effect of mousing over the `<hero-bios-and-contacts>` tag.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/highlight.png" alt="Highlighted bios")
:marked
<a id="providers"></a>
.l-main-section
:marked
## Define dependencies with providers
In this section we learn to write providers that deliver dependent services.
### Background
We get a service from a dependency injector by giving it a ***token***.
We usually let Angular handle this transaction for us by specifying a constructor parameter and its type.
The parameter type serves as the injector lookup *token*.
Angular passes this token to the injector and assigns the result to the parameter.
Here's a typical example:
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','ctor','app/hero-bios.component.ts (component constructor injection)')(format='.')
:marked
Angular asks the injector for the service associated with the `LoggerService` and
and assigns the returned value to the `logger` parameter.
Where did the injector get that value?
It may already have that value in its internal container.
It it doesn't, it may be able to make one with the help of a ***provider***.
A *provider* is a recipe for delivering a service associated with a *token*.
.l-sub-section
:marked
If the injector doesn't have a provider for the requested *token*, it delegates the request
to its parent injector, where the process repeats until there are no more injectors.
If the search is futile, the injector throws an error ... unless the request was [optional](#optional).
Let's return our attention to providers themselves.
:marked
A new injector has no providers.
Angular initializes the injectors it creates with some providers it cares about.
We have to register our _own_ application providers manually,
usually in the `providers` array of the `Component` or `Directive` metadata:
+makeExample('cb-dependency-injection/ts/app/app.component.ts','providers','app/app.component.ts (providers)')
:marked
### Defining providers
The simple class provider is the most typical by far.
We mention the class in the `providers` array and we're done.
+makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','class-provider','app/hero-bios.component.ts (class provider)')(format='.')
:marked
It's that simple because the most common injected service is an instance of a class.
But not every dependency can be satisfied by creating a new instance of a class.
We need other ways to deliver dependency values and that means we need other ways to specify a provider.
The `HeroOfTheMonthComponent` example demonstrates many of the alternatives and why we need them.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month" width="300px")
:marked
It's visually simple: a few properties and the output of a logger. The code behind it gives us plenty to talk about.
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','hero-of-the-month','hero-of-the-month.component.ts')
.l-main-section
a(id='provide')
:marked
#### The *provide* function
The imported Angular `provide` function creates an instance of
the Angular [Provider](../api/core/Provider-class.html) class.
The `provide` function takes a *token* and a *definition object*.
The *token* is usually a class but [it doesn't have to be](#tokens).
The *definition* object has one main property, (e.g. `useValue`) that indicates how the provider
should create or return the provided value.
.l-main-section
a(id='usevalue')
:marked
#### useValue - the *value provider*
Set the `useValue` property to a ***fixed value*** that the provider can return as the dependency object.
Use this technique to provide *runtime configuration constants* such as web-site base addresses and feature flags.
We often use a *value provider* in a unit test to replace a production service with a fake or mock.
The `HeroOfTheMonthComponent` example has two *value providers*.
The first provides an instance of the `Hero` class;
the second specifies a literal string resource:
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-value')(format='.')
:marked
The `Hero` provider token is a class which makes sense because the value is a `Hero`
and the consumer of the injected hero would want the type information.
The `TITLE` provider token is *not a class*.
It's a special kind of provider lookup key called an [OpaqueToken](#opaquetoken).
We often use an `OpaqueToken` when the dependency is a simple value like a string, a number, or a function.
The value of a *value provider* must be defined *now*. We can't create the value later.
Obviously the title string literal is immediately available.
The `someHero` variable in this example was set earlier in the file:
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','some-hero')
:marked
The other providers create their values *lazily* when they're needed for injection.
.l-main-section
a(id='useclass')
:marked
#### useClass - the *class provider*
The `useClass` provider creates and returns new instance of the specified class.
Use this technique to ***substitute an alternative implementation*** for a common or default class.
The alternative could implement a different strategy, extend the default class,
or fake the behavior of the real class in a test case.
We see two examples in the `HeroOfTheMonthComponent`:
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-class')(format='.')
:marked
The first provider is the *de-sugared*, expanded form of the most typical case in which the
class to be created (`HeroService`) is also the provider's injection token.
We wrote it in this long form to de-mystify the preferred short form.
The second provider substitutes the `DateLoggerService` for the `LoggerService`.
The `LoggerService` is already registered at the `AppComponent` level.
When _this component_ requests the `LoggerService`, it receives the `DateLoggerService` instead.
.l-sub-section
:marked
This component and its tree of child components receive the `DateLoggerService` instance.
Components outside the tree continue to receive the original `LoggerService` instance.
:marked
The `DateLoggerService` inherits from `LoggerService`; it appends the current date/time to each message:
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','date-logger-service','app/date-logger.service.ts')(format='.')
.l-main-section
a(id='useexisting')
:marked
#### useExisting - the *alias provider*
The `useExisting` provider maps one token to another.
In effect, the first token is an ***alias*** for the service associated with second token,
creating ***two ways to access the same service object***.
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-existing')
:marked
Narrowing an API through an aliasing interface is _one_ important use case for this technique.
We're aliasing for that very purpose here.
Imagine that the `LoggerService` had a large API (it's actually only three methods and a property).
We want to shrink that API surface to just the two members exposed by the `MinimalLogger` [*class-interface*](#class-interface):
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger','app/date-logger.service.ts (MinimalLogger)')(format='.')
:marked
The constructor's `logger` parameter is typed as `MinimalLogger` so only its two members are visible in TypeScript:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png" alt="MinimalLogger restricted API")
:marked
Angular actually sets the `logger` parameter to the injector's full version of the `LoggerService`
which happens to be the `DateLoggerService` thanks to the override provider registered previously via `useClass`.
The following image, which displays the logging date, confirms the point:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/date-logger-entry.png" alt="DateLoggerService entry" width="300px")
.l-main-section
a(id='usefactory')
:marked
#### useFactory - the *factory provider*
The `useFactory` provider creates a dependency object by calling a factory function
as seen in this example.
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-factory')
:marked
Use this technique to ***create a dependency object***
with a factory function whose inputs are some ***combination of injected services and local state***.
The *dependency object* doesn't have to be a class instance. It could be anything.
In this example, the *dependency object* is a string of the names of the runners-up
to the "Hero of the Month" contest.
The local state is the number `2`, the number of runners-up this component should show.
We execute `runnersUpFactory` immediately with `2`.
The `runnersUpFactory` itself isn't the provider factory function.
The true provider factory function is the function that `runnersUpFactory` returns.
+makeExample('cb-dependency-injection/ts/app/runners-up.ts','factory-synopsis','runners-up.ts (excerpt)')(format='.')
:marked
That returned function takes a winning `Hero` and a `HeroService` as arguments.
Angular supplies these arguments from injected values identified by
the two *tokens* in the `deps` array.
The two `deps` values are *tokens* that the injector uses
to provide these factory function dependencies.
After some undisclosed work, the function returns the string of names
and Angular injects it into the `runnersUp` parameter of the `HeroOfTheMonthComponent`.
.l-sub-section
:marked
The function retrieves candidate heroes from the `HeroService`,
takes `2` of them to be the runners-up, and returns their concatenated names.
Look at the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
for the full source code.
a(id="tokens")
.l-main-section
:marked
## Provider token alternatives: the *class-interface* and *OpaqueToken*
Angular dependency injection is easiest when the provider *token* is a class
that is also the type of the returned dependency object (what we usually call the *service*).
But the token doesn't have to be a class and even when it is a class,
it doesn't have to be the same type as the returned object.
That's the subject of our next section.
<a id="class-interface"></a>
### class-interface
In the previous *Hero of the Month* example, we used the `MinimalLogger` class
as the token for a provider of a `LoggerService`.
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','use-existing')
:marked
The `MinimalLogger` is an abstract class.
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger')(format='.')
:marked
We usually inherit from an abstract class.
But `LoggerService` doesn't inherit from `MinimalLogger`. *No class* inherits from it.
Instead, we use it like an interface.
Look again at the declaration for `DateLoggerService`
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','date-logger-service-signature')(format='.')
:marked
`DateLoggerService` inherits (extends) from `LoggerService`, not `MinimalLogger`.
The `DateLoggerService` *implements* `MinimalLogger` as if `MinimalLogger` were an *interface*.
We call a class used in this way a ***class-interface***.
The key benefit of a *class-interface* is that we can get the strong-typing of an interface
and we can ***use it as a provider token*** in the same manner as a normal class.
A ***class-interface*** should define *only* the members that its consumers are allowed to call.
Such a narrowing interface helps decouple the concrete class from its consumers.
The `MinimalLogger` defines just two of the `LoggerClass` members.
.l-sub-section
:marked
#### Why *MinimalLogger* is a class and not an interface
We can't use an interface as a provider token because
interfaces are not JavaScript objects.
They exist only in the TypeScript design space.
They disappear after the code is transpiled to JavaScript.
A provider token must be a real JavaScript object of some kind:
a function, an object, a string ... a class.
Using a class as an interface gives us the characteristics of an interface in a JavaScript object.
The minimize memory cost, the class should have *no implementation*.
The `MinimalLogger` transpiles to this unoptimized, pre-minified JavaScript:
+makeExample('cb-dependency-injection/ts/app/date-logger.service.ts','minimal-logger-transpiled')(format='.')
:marked
It never grows larger no matter how many members we add *as long as they are typed but not implemented*.
a(id='opaque-token')
:marked
### OpaqueToken
Dependency objects can be simple values like dates, numbers and strings or
shapeless objects like arrays and functions.
Such objects don't have application interfaces and therefore aren't well represented by a class.
They're better represented by a token that is both unique and symbolic,
a JavaScript object that has a friendly name but won't conflict with
another token that happens to have the same name.
The `OpaqueToken` has these characteristics.
We encountered them twice in the *Hero of the Month* example,
in the *title* value provider and in the *runnersUp* factory provider.
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','provide-opaque-token')(format='.')
:marked
We created the `TITLE` token like this:
+makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','opaque-token')(format='.')
a(id="di-inheritance")
.l-main-section
:marked
## Inject into a derived class
We must take care when writing a component that inherits from another component.
If the base component has injected dependencies,
we must re-provide and re-inject them in the derived class
and then pass them down to the base class through the constructor.
In this contrived example, `SortedHeroesComponent` inherits from `HeroesBaseComponent`
to display a *sorted* list of heroes.
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/sorted-heroes.png" alt="Sorted Heroes")
:marked
The `HeroesBaseComponent` could stand on its own.
It demands its own instance of the `HeroService` to get heroes
and displays them in the order they arrive from the database.
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','heroes-base','app/sorted-heroes.component.ts (HeroesBaseComponent)')
.l-sub-section
:marked
We strongly prefer simple constructors. They should do little more than initialize variables.
This rule makes the component safe to construct under test without fear that it will do something dramatic like talk to the server.
That's why we call the `HeroService` from within the `ngOnInit` rather than the constructor.
We explain the mysterious `_afterGetHeroes` below.
:marked
Users want to see the heroes in alphabetical order.
Rather than modify the original component, we sub-class it and create a
`SortedHeroesComponent` that sorts the heroes before presenting them.
The `SortedHeroesComponent` lets the base class fetch the heroes.
(we said it was contrived).
Unfortunately, Angular cannot inject the `HeroService` directly into the base class.
We must provide the `HeroService` again for *this* component,
then pass it down to the base class inside the constructor.
+makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','sorted-heroes','app/sorted-heroes.component.ts (SortedHeroesComponent)')
:marked
Now take note of the `_afterGetHeroes` method.
Our first instinct was to create an `ngOnInit` method in `SortedHeroesComponent` and do the sorting there.
But Angular calls the *derived* class's `ngOnInit` *before* calling the base class's `ngOnInit`
so we'd be sorting the heroes array *before they arrived*. That produces a nasty error.
Overriding the base class's `_afterGetHeroes` method solves the problem
These complications argue for *avoiding component inheritance*.
a(id="find-parent")
.l-main-section
:marked
## Find a parent component by injection
Application components often need to share information.
We prefer the more loosely coupled techniques such as data binding and service sharing.
But sometimes it makes sense for one component to have a direct reference to another component
perhaps to access values or call methods on that component.
Obtaining a component reference is a bit tricky in Angular.
Although an Angular application is a tree of components,
there is no public API for inspecting and traversing that tree.
There is an API for acquiring a child reference
(checkout `Query`, `QueryList`, `ViewChildren`, and `ContentChildren`).
There is no public API for acquiring a parent reference.
But because every component instance is added to an injector's container,
we can use Angular dependency injection to reach a parent component.
This section describes some techniques for doing that.
<a id="known-parent"></a>
### Find a parent component of known type
We use standard class injection to acquire a parent component whose type we know.
In the following example, the parent `AlexComponent` has several children including a `CathyComponent`:
a(id='alex')
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-1','parent-finder.component.ts (AlexComponent v.1)')(format='.')
:marked
*Cathy* reports whether or not she has access to *Alex*
after injecting an `AlexComponent` into her constructor:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','cathy','parent-finder.component.ts (CathyComponent)')(format='.')
:marked
We added the [@Optional](#optional) qualifier for safety but
the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
confirms that the `alex` parameter is set.
<a id="base-parent"></a>
### Cannot find a parent by its base class
What if we do *not* know the concrete parent component class?
A re-usable component might be a child of multiple components.
Imagine a component for rendering breaking news about a financial instrument.
For sound (cough) business reasons, this news component makes frequent calls
directly into its parent instrument as changing market data stream by.
The app probably defines more than a dozen financial instrument components.
If we're lucky, they all implement the same base class
whose API our `NewsComponent` understands.
.l-sub-section
:marked
Looking for components that implement an interface would be better.
That's not possible because TypeScript interfaces disappear from the transpiled JavaScript
which doesn't support interfaces. There's no artifact we could look for.
:marked
We're not claiming this is good design.
We are asking *can a component inject its parent via the parent's base class*?
The sample's `CraigComponent` explores this question. [Looking back](#alex)
we see that the `Alex` component *extends* (*inherits*) from a class named `Base`.
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (Alex class signature)')(format='.')
:marked
The `CraigComponent` tries to inject `Base` into its `alex` constructor parameter and reports if it succeeded.
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','craig','parent-finder.component.ts (CraigComponent)')(format='.')
:marked
Unfortunately, this does not work.
The [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)
confirms that the `alex` parameter is null.
*We cannot inject a parent by its base class.*
<a id="class-interface-parent"></a>
### Find a parent by its class-interface
We can find a parent component with a [class-interface](#class-interface).
The parent must cooperate by providing an *alias* to itself in the name of a *class-interface* token.
Recall that Angular always adds a component instance to its own injector;
that's why we could inject *Alex* into *Carol* [earlier](#known-parent).
We write an [*alias provider*](#useexisting) &mdash; a `provide` function with a `useExisting` definition &mdash;
that creates an *alternative* way to inject the same component instance
and add that provider to the `providers` array of the `@Component` metadata for the `AlexComponent`:
a(id="alex-providers")
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
:marked
[Parent](#parent-token) is the provider's *class-interface* token.
The [*forwardRef*](#forwardref) breaks the circular reference we just created by having the `AlexComponent` refer to itself.
*Carol*, the third of *Alex*'s child components, injects the parent into its `parent` parameter, the same way we've done it before:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','carol-class','parent-finder.component.ts (CarolComponent class)')(format='.')
:marked
Here's *Alex* and family in action:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/alex.png" alt="Alex in action")
a(id="parent-tree")
:marked
### Find the parent in a tree of parents
Imagine one branch of a component hierarchy: *Alice* -> *Barry* -> *Carol*.
Both *Alice* and *Barry* implement the `Parent` *class-interface*.
*Barry* is the problem. He needs to reach his parent, *Alice*, and also be a parent to *Carol*.
That means he must both *inject* the `Parent` *class-interface* to get *Alice* and
*provide* a `Parent` to satisfy *Carol*.
Here's *Barry*:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','barry','parent-finder.component.ts (BarryComponent)')(format='.')
:marked
*Barry*'s `providers` array looks just like [*Alex*'s](#alex-providers).
If we're going to keep writing [*alias providers*](#useexisting) like this we should create a [helper function](#provideparent).
For now, focus on *Barry*'s constructor:
+makeTabs(
'cb-dependency-injection/ts/app/parent-finder.component.ts, cb-dependency-injection/ts/app/parent-finder.component.ts',
'barry-ctor, carol-ctor',
'Barry\'s constructor, Carol\'s constructor')(format='.')
:marked
:marked
It's identical to *Carol*'s constructor except for the additional `@SkipSelf` decorator.
`@SkipSelf` is essential for two reasons:
1. It tell the injector to start its search for a `Parent` dependency in a component *above* itself,
which *is* what parent means.
2. Angular throws a cyclic dependency error if we omit the `@SkipSelf` decorator.
`Cannot instantiate cyclic dependency! (BethComponent -> Parent -> BethComponent)`
Here's *Alice*, *Barry* and family in action:
figure.image-display
img(src="/resources/images/cookbooks/dependency-injection/alice.png" alt="Alice in action")
a(id="parent-token")
:marked
### The *Parent* class-interface
We [learned earlier](#class-interface) that a *class-interface* is an abstract class used as an interface rather than as a base class.
Our example defines a `Parent` *class-interface* .
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','parent','parent-finder.component.ts (Parent class-interface)')(format='.')
:marked
The `Parent` *class-interface* defines a `name` property with a type declaration but *no implementation*.,
The `name` property is the only member of a parent component that a child component can call.
Such a narrowing interface helps decouple the child component class from its parent components.
A component that could serve as a parent *should* implement the *class-interface* as the `AliceComponent` does:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alice-class-signature','parent-finder.component.ts (AliceComponent class signature)')(format='.')
:marked
Doing so adds clarity to the code. But it's not technically necessary.
Although the `AlexComponent` has a `name` property (as required by its `Base` class)
its class signature doesn't mention `Parent`:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-class-signature','parent-finder.component.ts (AlexComponent class signature)')(format='.')
.l-sub-section
:marked
The `AlexComponent` *should* implement `Parent` as a matter of proper style.
It doesn't in this example *only* to demonstrate that the code will compile and run without the interface
a(id="provideparent")
:marked
### A *provideParent* helper function
Writing variations of the same parent *alias provider* gets old quickly,
especially this awful mouthful with a [*forwardRef*](#forwardref):
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers')(format='.')
:marked
We can extract that logic into a helper function like this:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','provide-the-parent')(format='.')
:marked
Now we can add a simpler, more meaningful parent provider to our components:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alice-providers')(format='.')
:marked
We can do better. The current version of the helper function can only alias the `Parent` *class-interface*.
Our application might have a variety of parent types, each with its own *class-interface* token.
Here's a revised version that defaults to `parent` but also accepts an optional second parameter for a different parent *class-interface*.
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','provide-parent')(format='.')
:marked
And here's how we could use it with a different parent type:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','beth-providers')(format='.')
:marked
a(id="forwardref")
.l-main-section
:marked
## Break circularities with a forward class reference (*forwardRef*)
The order of class declaration matters in TypeScript.
We can't refer directly to a class until it's been defined.
This isn't usually a problem, especially if we adhere to the recommended *one class per file* rule.
But sometimes circular references are unavoidable.
We're in a bind when class 'A refers to class 'B' and 'B' refers to 'A'.
One of them has to be defined first.
The Angular `forwardRef` function creates an *indirect* reference that Angular can resolve later.
The *Parent Finder* sample is full of circular class references that are impossible to break.
In the [*Alex/Cathy* example](#known-parent) above:
* the `AlexComponent` lists the `CathyComponent` in its component metadata `directives` array
so it can display *Cathy* in its template.
* the `CathyComponent` constructor injects the parent `AlexComponent` which means that the `alex` parameter
of its constructor has the `AlexComponent` type.
*Alex* refers to *Cathy* and *Cathy* refers to *Alex*. We're stuck. We must define one of them first.
We defined *Alex* first and built its `C_DIRECTIVES` array with a forward reference to *Cathy*:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','C_DIRECTIVES','parent-finder.component.ts (C_DIRECTIVES)')(format='.')
:marked
.l-sub-section
:marked
Defining *Alex* and *Cathy* in separate files won't help.
*Alex* would have to import *Cathy* and *Cathy* would have to import *Alex*.
We *had* to define *Alex* first because,
while we can add `forwardRef(CathyComponent)` to *Alex*'s `directives` array,
we can't write `public alex: forwardRef(AlexComponent))` in *Cathy*'s constructor.
:marked
We face a similar dilemma when a class makes *a reference to itself*
as does the `AlexComponent` in its `providers` array.
The `providers` array is a property of the `@Component` decorator function which must
appear *above* the class definition.
Again we break the circularity with `forwardRef`:
+makeExample('cb-dependency-injection/ts/app/parent-finder.component.ts','alex-providers','parent-finder.component.ts (AlexComponent providers)')(format='.')
:marked

View File

@ -186,12 +186,9 @@ figure.image-display
Now we implement those two mouse event handlers:
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')(format=".")
:marked
Notice that they delegate to a helper method to set the color.
Notice that they delegate to a helper method that sets the color via a private local variable, `_el`.
We revise the constructor to capture the `ElementRef.nativeElement` in `_el`.
We no longer need the constructor body but
we still want the injected `ElementRef`.
We revise the constructor signature to capture the injected `ElementRef` in a private variable
and clear the body.
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')(format=".")
:marked
Here's the updated directive:

View File

@ -411,13 +411,18 @@ include ../_util-fns
- var decorated = lang == 'dart' ? 'annotated' : 'decorated'
- var any_decorator = lang == 'dart' ? '' : 'TypeScript generates metadata for any class with a decorator, and any decorator will do.'
.callout.is-helpful
header Always add @Injectable()
header Suggestion: add @Injectable() to every service class
:marked
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
and, therefore, do not technically require it. Here's why:
ul(style="font-size:inherit")
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when we add a dependency later.
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why #{a_decorator} is missing.
:marked
Although we recommend applying `@Injectable` to all service classes, do not feel bound by it.
Some developers prefer to add it only where needed and that's a reasonable policy too.
.l-sub-section
:marked
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?

View File

@ -312,8 +312,7 @@ figure.image-display
+makeExample('pipes/ts/app/flying-heroes.pipe.ts','filter')(format='.')
We can derive a `FlyingHeroesImpureComponent` that we derive from the `FlyingHeroesComponent`.
+makeExample('pipes/ts/app/flying-heroes.component.ts','impure-component',
'app/flying-heroes.component.ts (FlyingHeroesImpureComponent)')(format='.')
+makeExample('pipes/ts/app/flying-heroes.component.ts','impure-component','app/flying-heroes.component.ts (FlyingHeroesImpureComponent)')(format='.')
:marked
The only substantive change is the pipe.
We can confirm in the [live example](/resources/live-examples/pipes/ts/plnkr.html)

View File

@ -1386,9 +1386,10 @@ code-example(format="." language="bash").
`HeroService` and (perhaps) mocking it.
<a name="browser-url-styles"></a>
<a id="location-strategy"></a>
.l-main-section
:marked
## Appendix: Browser URL styles
## Appendix: *LocationStrategy* and browser URL styles
When the router navigates to a new component view, it updates the browser's location and history
with a URL for that view.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB