docs: add DI cookbook
|
@ -74,6 +74,7 @@ var _exampleBoilerplateFiles = [
|
|||
'package.json',
|
||||
'styles.css',
|
||||
'tsconfig.json',
|
||||
'tslint.json',
|
||||
'typings.json'
|
||||
];
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@ package.json
|
|||
karma.conf.js
|
||||
karma-test-shim.js
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
npm-debug*.
|
||||
**/protractor.config.js
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
})
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
**/*.js
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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};
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// #docregion
|
||||
export class Hero{
|
||||
constructor(
|
||||
public id: number,
|
||||
public name:string,
|
||||
public description?:string,
|
||||
public phone?:string) {
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 { }
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,10 @@
|
|||
// #docregion
|
||||
import {Injectable} from 'angular2/core';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
|
||||
getUserById(userId:number):any{
|
||||
return {name:'Bombasto',role:'Admin'};
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"description": "Dependency Injection",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -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": {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -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"
|
||||
|
|
|
@ -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 — *any* decorator — 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) — a `provide` function with a `useExisting` definition —
|
||||
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
|
|
@ -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:
|
||||
|
|
|
@ -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`?
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 8.0 KiB |