diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..bab156b63a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[*.jade] +max_line_length = 0 +trim_trailing_whitespace = false + +# Indentation override +#[lib/**.js] +#[{package.json,.travis.yml}] +#[**/**.js] diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index bede42df52..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -// Place your settings in this file to overwrite default and user settings. -{ - // Controls the rendering size of tabs in characters. Accepted values: "auto", 2, 4, 6, etc. If set to "auto", the value will be guessed when a file is opened. - "editor.tabSize": 2, - // Controls if the editor will insert spaces for tabs. Accepted values: "auto", true, false. If set to "auto", the value will be guessed when a file is opened. - "editor.insertSpaces": true, - // When enabled, will trim trailing whitespace when you save a file. - "files.trimTrailingWhitespace": false, - // Specifies the folder path containing the tsserver and lib*.d.ts files to use. - "typescript.tsdk": "public/docs/_examples/node_modules/typescript/lib" -} diff --git a/gulpfile.js b/gulpfile.js index 774c1c4381..44baa44792 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -69,11 +69,13 @@ var _excludeMatchers = _excludePatterns.map(function(excludePattern){ }); var _exampleBoilerplateFiles = [ + '.editorconfig', 'karma.conf.js', 'karma-test-shim.js', 'package.json', 'styles.css', 'tsconfig.json', + 'tslint.json', 'typings.json' ]; diff --git a/public/docs/_examples/.editorconfig b/public/docs/_examples/.editorconfig new file mode 100644 index 0000000000..f2abacf6d3 --- /dev/null +++ b/public/docs/_examples/.editorconfig @@ -0,0 +1,20 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +# Indentation override +#[lib/**.js] +#[{package.json,.travis.yml}] +#[**/**.js] diff --git a/public/docs/_examples/.gitignore b/public/docs/_examples/.gitignore index 7a15251d4f..d821c82bea 100644 --- a/public/docs/_examples/.gitignore +++ b/public/docs/_examples/.gitignore @@ -1,3 +1,4 @@ +.editorconfig styles.css typings typings.json @@ -6,5 +7,6 @@ package.json karma.conf.js karma-test-shim.js tsconfig.json +tslint.json npm-debug*. **/protractor.config.js diff --git a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts index 9cf0b95970..1f42c5773f 100644 --- a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts +++ b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts @@ -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 diff --git a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts index 6a26780057..4380174c8f 100644 --- a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts +++ b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts @@ -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,21 +23,21 @@ export class HighlightDirective { // #enddocregion highlight */ // #docregion full + +// #docregion defaultColor + @Input() set defaultColor(colorName:string){ + this._defaultColor = colorName || this._defaultColor; + } +// #enddocregion defaultColor // #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; - } - // #enddocregion defaultColor // #docregion class-1 - - constructor(private el: ElementRef) { } + 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 diff --git a/public/docs/_examples/cb-dependency-injection/e2e-spec.js b/public/docs/_examples/cb-dependency-injection/e2e-spec.js new file mode 100644 index 0000000000..aeb0e68b17 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/e2e-spec.js @@ -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'); + }); + }) +}); diff --git a/public/docs/_examples/cb-dependency-injection/ts/.gitignore b/public/docs/_examples/cb-dependency-injection/ts/.gitignore new file mode 100644 index 0000000000..cf44e148ba --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/.gitignore @@ -0,0 +1 @@ +**/*.js \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/app.component.html b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.html new file mode 100644 index 0000000000..c27281f2af --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.html @@ -0,0 +1,39 @@ +

DI Cookbook

+
+

Logged in user

+
Name: {{userContext.name}}
+
Role: {{userContext.role}}
+
+ +
+

Hero Bios

+ +
+ + +
+

Hero Bios and Contacts

+
+ +
+
+ + +
+ +
+ + +
+

Unsorted Heroes

+ +
+ +
+

Sorted Heroes

+ +
+ +
+ +
\ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts new file mode 100644 index 0000000000..2e4485f665 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/date-logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/date-logger.service.ts new file mode 100644 index 0000000000..125ba37c2b --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/date-logger.service.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-bio.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-bio.component.ts new file mode 100644 index 0000000000..6b006fcc56 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-bio.component.ts @@ -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:` +

{{hero.name}}

+ + `, + // #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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-bios.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-bios.component.ts new file mode 100644 index 0000000000..9d14daf648 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-bios.component.ts @@ -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:` + + + `, + 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:` + + + `, + // #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 \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-cache.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-cache.service.ts new file mode 100644 index 0000000000..c0c7ce581f --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-cache.service.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-contact.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-contact.component.ts new file mode 100644 index 0000000000..42163d3f3e --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-contact.component.ts @@ -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:` +
Phone #: {{phoneNumber}} + !!!
` +}) +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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-data.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-data.ts new file mode 100644 index 0000000000..18133fd771 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-data.ts @@ -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}; + } +} diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero-of-the-month.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero-of-the-month.component.ts new file mode 100644 index 0000000000..02dcae4824 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-of-the-month.component.ts @@ -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 = ` +

{{title}}

+
Winner: {{heroOfTheMonth.name}}
+
Reason for award: {{heroOfTheMonth.description}}
+
Runners-up: {{runnersUp}}
+ +

Logs:

+
+
{{log}}
+
+ `; + +// #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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts new file mode 100644 index 0000000000..274753ae8d --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts @@ -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 = [ + 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{ + return this._heros; + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts new file mode 100644 index 0000000000..51ce8ebbff --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts @@ -0,0 +1,9 @@ +// #docregion +export class Hero{ + constructor( + public id: number, + public name:string, + public description?:string, + public phone?:string) { + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/highlight.directive.ts b/public/docs/_examples/cb-dependency-injection/ts/app/highlight.directive.ts new file mode 100644 index 0000000000..6d054e1a0b --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/highlight.directive.ts @@ -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; + } +} diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts new file mode 100644 index 0000000000..ecc75350e3 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts @@ -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); + } +} diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/main.ts b/public/docs/_examples/cb-dependency-injection/ts/app/main.ts new file mode 100644 index 0000000000..b1e35e2a67 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/main.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/parent-finder.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/parent-finder.component.ts new file mode 100644 index 0000000000..4099ec6af9 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/parent-finder.component.ts @@ -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 = ` +
+

{{name}}

+

My parent is {{parent?.name}}

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

Craig

+ {{alex ? 'Found' : 'Did not find'}} Alex via the base class. +
` +}) +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 = ` +
+
+

{{name}}

+

My parent is {{parent?.name}}

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

{{name}}

+ + + +
`, +// #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: ` +
+

{{name}}

+ + + + +
`, + 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: ` +
+

Cathy

+ {{alex ? 'Found' : 'Did not find'}} Alex via the component class.
+
` +}) +export class CathyComponent { + constructor( @Optional() public alex: AlexComponent ) { } +} +// #enddocregion cathy + +///////// ParentFinder ////// +@Component({ + selector: 'parent-finder', + template: ` +

Parent Finder

+ + `, + directives: [ AlexComponent, AliceComponent ] +}) +export class ParentFinderComponent { } diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts new file mode 100644 index 0000000000..a2d79df35e --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes.component.ts new file mode 100644 index 0000000000..2a45367044 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes.component.ts @@ -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: `
{{hero.name}}
`, + providers: [HeroService] +}) +export class HeroesBaseComponent implements OnInit { + constructor(private _heroService: HeroService) { } +// #enddocregion injection + + heroes: Array; + + 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: `
{{hero.name}}
`, + 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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/user-context.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/user-context.service.ts new file mode 100644 index 0000000000..b241675d31 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/user-context.service.ts @@ -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 diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts new file mode 100644 index 0000000000..f2d44d7a26 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts @@ -0,0 +1,10 @@ +// #docregion +import {Injectable} from 'angular2/core'; + +@Injectable() +export class UserService { + + getUserById(userId:number):any{ + return {name:'Bombasto',role:'Admin'}; + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/example-config.json b/public/docs/_examples/cb-dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-dependency-injection/ts/index.html b/public/docs/_examples/cb-dependency-injection/ts/index.html new file mode 100644 index 0000000000..66db40eb30 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/index.html @@ -0,0 +1,44 @@ + + + + + Dependency Injection + + + + + + + + + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-dependency-injection/ts/plnkr.json b/public/docs/_examples/cb-dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..d20b01cf1d --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Dependency Injection", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":["cookbook"] +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/sample.css b/public/docs/_examples/cb-dependency-injection/ts/sample.css new file mode 100644 index 0000000000..a8b59efd05 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/sample.css @@ -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; +} \ No newline at end of file diff --git a/public/docs/_examples/package.json b/public/docs/_examples/package.json index 73e8969a00..0c6772d3a2 100644 --- a/public/docs/_examples/package.json +++ b/public/docs/_examples/package.json @@ -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", diff --git a/public/docs/_examples/quickstart/ts/package.1.json b/public/docs/_examples/quickstart/ts/package.1.json index f399545abd..ad596c2088 100644 --- a/public/docs/_examples/quickstart/ts/package.1.json +++ b/public/docs/_examples/quickstart/ts/package.1.json @@ -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", diff --git a/public/docs/_examples/tslint.json b/public/docs/_examples/tslint.json new file mode 100644 index 0000000000..276453f4f5 --- /dev/null +++ b/public/docs/_examples/tslint.json @@ -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" + ] + } +} diff --git a/public/docs/dart/latest/cookbook/_data.json b/public/docs/dart/latest/cookbook/_data.json index 4d4757ee57..ce8315d497 100644 --- a/public/docs/dart/latest/cookbook/_data.json +++ b/public/docs/dart/latest/cookbook/_data.json @@ -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 diff --git a/public/docs/dart/latest/cookbook/dependency-injection.jade b/public/docs/dart/latest/cookbook/dependency-injection.jade new file mode 100644 index 0000000000..f8df2a84a6 --- /dev/null +++ b/public/docs/dart/latest/cookbook/dependency-injection.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/_data.json b/public/docs/js/latest/cookbook/_data.json index dc91a7b4a2..a2040caccf 100644 --- a/public/docs/js/latest/cookbook/_data.json +++ b/public/docs/js/latest/cookbook/_data.json @@ -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": { diff --git a/public/docs/js/latest/cookbook/dependency-injection.jade b/public/docs/js/latest/cookbook/dependency-injection.jade new file mode 100644 index 0000000000..f8df2a84a6 --- /dev/null +++ b/public/docs/js/latest/cookbook/dependency-injection.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index fe2f7b8954..e4363318ef 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -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" diff --git a/public/docs/ts/latest/cookbook/dependency-injection.jade b/public/docs/ts/latest/cookbook/dependency-injection.jade new file mode 100644 index 0000000000..c5fdcd0a7c --- /dev/null +++ b/public/docs/ts/latest/cookbook/dependency-injection.jade @@ -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. + + +: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 + + +: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='.') + + +.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. + + + +.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. + + +.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. + + + 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 `` element between the `` tags. + Angular *projects* (*transcludes*) the corresponding `HeroContactComponent` into the `HeroBioComponent` view, + placing it in the `` 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. +
+ `EXCEPTION: No provider for LoggerService! (HeroContactComponent -> LoggerService)` + + +: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 `
` 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 `` tag. +figure.image-display + img(src="/resources/images/cookbooks/dependency-injection/highlight.png" alt="Highlighted bios") +:marked + + +.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. + + + ### 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. + + + ### 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. + + + ### 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.* + + + ### 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 diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade index b9bc22fcf9..7452ac19c8 100644 --- a/public/docs/ts/latest/guide/attribute-directives.jade +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -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. - - 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. + 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`. + +makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')(format=".") :marked Here's the updated directive: diff --git a/public/docs/ts/latest/guide/dependency-injection.jade b/public/docs/ts/latest/guide/dependency-injection.jade index d34df913d2..72b7ed695c 100644 --- a/public/docs/ts/latest/guide/dependency-injection.jade +++ b/public/docs/ts/latest/guide/dependency-injection.jade @@ -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 Future proofing: No need to remember @Injectable() when we add a dependency later. li Consistency: 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`? diff --git a/public/docs/ts/latest/guide/pipes.jade b/public/docs/ts/latest/guide/pipes.jade index 54b0401aa9..42abb48a7e 100644 --- a/public/docs/ts/latest/guide/pipes.jade +++ b/public/docs/ts/latest/guide/pipes.jade @@ -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) diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index cec2c638bb..594d2d467a 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -1386,9 +1386,10 @@ code-example(format="." language="bash"). `HeroService` and (perhaps) mocking it. + .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. diff --git a/public/resources/images/cookbooks/dependency-injection/alex.png b/public/resources/images/cookbooks/dependency-injection/alex.png new file mode 100644 index 0000000000..06a9762024 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/alex.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/alice.png b/public/resources/images/cookbooks/dependency-injection/alice.png new file mode 100644 index 0000000000..48d311b37f Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/alice.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/date-logger-entry.png b/public/resources/images/cookbooks/dependency-injection/date-logger-entry.png new file mode 100644 index 0000000000..fe7c9953c3 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/date-logger-entry.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png b/public/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png new file mode 100644 index 0000000000..bc6ee5da9a Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png b/public/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png new file mode 100644 index 0000000000..62211bbae6 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png b/public/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png new file mode 100644 index 0000000000..9b196cbd0b Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/hero-bios.png b/public/resources/images/cookbooks/dependency-injection/hero-bios.png new file mode 100644 index 0000000000..df3fd2140d Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/hero-bios.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/hero-of-month.png b/public/resources/images/cookbooks/dependency-injection/hero-of-month.png new file mode 100644 index 0000000000..f7d485df3e Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/hero-of-month.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/highlight.png b/public/resources/images/cookbooks/dependency-injection/highlight.png new file mode 100644 index 0000000000..e3dced717c Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/highlight.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/logged-in-user.png b/public/resources/images/cookbooks/dependency-injection/logged-in-user.png new file mode 100644 index 0000000000..4a43da33c9 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/logged-in-user.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png b/public/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png new file mode 100644 index 0000000000..1f02db8d76 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png differ diff --git a/public/resources/images/cookbooks/dependency-injection/sorted-heroes.png b/public/resources/images/cookbooks/dependency-injection/sorted-heroes.png new file mode 100644 index 0000000000..6adfeae498 Binary files /dev/null and b/public/resources/images/cookbooks/dependency-injection/sorted-heroes.png differ diff --git a/tools/plunker-builder/plunkerBuilder.js b/tools/plunker-builder/plunkerBuilder.js index cb1c405de9..bf784f1e32 100644 --- a/tools/plunker-builder/plunkerBuilder.js +++ b/tools/plunker-builder/plunkerBuilder.js @@ -107,14 +107,16 @@ function initConfigAndCollectFileNames(configFileName) { } }); // var defaultExcludes = [ '!**/node_modules/**','!**/typings/**','!**/tsconfig.json', '!**/*plnkr.json', '!**/*plnkr.html', '!**/*plnkr.no-link.html' ]; - var defaultExcludes = [ + var defaultExcludes = [ '!**/typings/**', '!**/typings.json', - '!**/tsconfig.json', - '!**/*plnkr.*', - '!**/package.json', + '!**/tsconfig.json', + '!**/*plnkr.*', + '!**/package.json', '!**/example-config.json', - '!**/*.spec.*' + '!**/*.spec.*', + '!**/tslint.json', + '!**/.editorconfig' ]; Array.prototype.push.apply(gpaths, defaultExcludes); @@ -263,4 +265,4 @@ function escapeHtml(unsafe) { // fs.writeFileSync(outputFn, html, 'utf-8' ); // } // }); -//} \ No newline at end of file +//}