From dcdfad3aec36c513208a0ae3cbc4307019a3ed69 Mon Sep 17 00:00:00 2001 From: Torgeir Helgevold Date: Sat, 26 Mar 2016 12:18:13 -0400 Subject: [PATCH 1/3] docs: add DI cookbook --- .../cb-dependency-injection/e2e-spec.js | 41 +++++ .../cb-dependency-injection/ts/.gitignore | 1 + .../ts/app/app.component.ts | 47 ++++++ .../ts/app/bio.component.ts | 27 ++++ .../ts/app/contact-details.component.ts | 23 +++ .../ts/app/date-logger.service.ts | 9 ++ .../ts/app/hero-bios.component.ts | 27 ++++ .../ts/app/hero-of-the-month.component.ts | 28 ++++ .../ts/app/hero.service.ts | 27 ++++ .../cb-dependency-injection/ts/app/hero.ts | 5 + .../ts/app/logger.service.ts | 18 +++ .../cb-dependency-injection/ts/app/main.ts | 9 ++ .../ts/app/runners-up-provider.service.ts | 12 ++ .../ts/app/runners-up.ts | 11 ++ .../ts/app/sorted-heroes-base.ts | 22 +++ .../ts/app/sorted-heroes.component.ts | 16 ++ .../ts/app/user-context.service.ts | 24 +++ .../ts/app/user.service.ts | 10 ++ .../ts/example-config.json | 0 .../cb-dependency-injection/ts/index.html | 39 +++++ .../cb-dependency-injection/ts/plnkr.json | 9 ++ .../cb-dependency-injection/ts/sample.css | 5 + public/docs/ts/latest/cookbook/_data.json | 5 + .../latest/cookbook/dependency-injection.jade | 143 ++++++++++++++++++ .../dependency-injection/hero-bios.png | Bin 0 -> 23420 bytes .../dependency-injection/hero-of-month.png | Bin 0 -> 16797 bytes .../dependency-injection/logged-in-user.png | Bin 0 -> 8274 bytes .../dependency-injection/sorted-heroes.png | Bin 0 -> 8187 bytes 28 files changed, 558 insertions(+) create mode 100644 public/docs/_examples/cb-dependency-injection/e2e-spec.js create mode 100644 public/docs/_examples/cb-dependency-injection/ts/.gitignore create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/date-logger.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-bios.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-of-the-month.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/main.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/user-context.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/example-config.json create mode 100644 public/docs/_examples/cb-dependency-injection/ts/index.html create mode 100644 public/docs/_examples/cb-dependency-injection/ts/plnkr.json create mode 100644 public/docs/_examples/cb-dependency-injection/ts/sample.css create mode 100644 public/docs/ts/latest/cookbook/dependency-injection.jade create mode 100644 public/resources/images/cookbooks/dependency-injection/hero-bios.png create mode 100644 public/resources/images/cookbooks/dependency-injection/hero-of-month.png create mode 100644 public/resources/images/cookbooks/dependency-injection/logged-in-user.png create mode 100644 public/resources/images/cookbooks/dependency-injection/sorted-heroes.png 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..02237faab6 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/e2e-spec.js @@ -0,0 +1,41 @@ +describe('Dependency Injection', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should render DI samples', function () { + var loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0); + expect(loggedInUser).toBeDefined(); + + loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0); + expect(loggedInUser).toBeDefined(); + + var sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0); + expect(sortedHeroes).toBeDefined(); + + var sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Mr. Nice" and position()=2]')).get(0); + expect(sortedHero).toBeDefined(); + + sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0); + expect(sortedHero).toBeDefined(); + + sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0); + expect(sortedHero).toBeDefined(); + + var heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0); + expect(heroOfTheMonth).toBeDefined(); + + var heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0); + expect(heroBios).toBeDefined(); + + var magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0); + expect(magmaText).toBeDefined(); + + var magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0); + expect(magmaPhone).toBeDefined(); + + var runnersUp = element.all(by.xpath('//h4[text()="Other candidates RubberMan, Mr. Nice"]')).get(0); + expect(runnersUp).toBeDefined(); + }); +}); 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.ts b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts new file mode 100644 index 0000000000..fb00ccc80d --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts @@ -0,0 +1,47 @@ +// #docregion +import {Component,OnInit} from 'angular2/core'; +import {LoggerService} from './logger.service'; +import {UserContext} from './user-context.service'; +import {Heroes} from './hero-bios.component'; +import {SortedHeroes} from './sorted-heroes.component'; +import {HeroOfTheMonth} from './hero-of-the-month.component'; + +@Component({ + selector: 'my-app', + directives:[Heroes,SortedHeroes,HeroOfTheMonth], + template: + `

DI Components

+
+

Logged in user

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

Sorted Heroes

+ +
+ +
+

Hero of the month

+ +
+ +
+

Hero Bios

+ +
` +}) + +export class AppComponent implements OnInit { + + private userId:number = 1; + + constructor(private _logger:LoggerService, private _userContext:UserContext){ + this._userContext.loadUser(this.userId); + } + + ngOnInit(){ + this._logger.logInfo('AppComponent initialized'); + } +} diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts new file mode 100644 index 0000000000..b993d57ff5 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts @@ -0,0 +1,27 @@ +// #docregion +import {Component,Input,OnInit} from 'angular2/core'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; + +@Component({ + selector:'bio', + template:`

{{hero.name}}

+
+ + +
`, + providers:[HeroService] +}) + +export class Bio implements OnInit{ + + @Input() heroIndex:number; + private hero:Hero; + + constructor(private _heroService:HeroService){ + } + + ngOnInit(){ + this.hero = this._heroService.getHeroById(this.heroIndex); + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts new file mode 100644 index 0000000000..d8f8331636 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts @@ -0,0 +1,23 @@ +// #docregion +import {Component, Host, Optional, OnInit, ElementRef} from 'angular2/core'; + +import {HeroService} from './hero.service'; + +@Component({ + selector:'contact-details', + template:'
Phone #: {{phoneNumber}}
' +}) + +export class ContactDetails implements OnInit{ + + private phoneNumber:string; + constructor(@Optional() @Host() private _heroService:HeroService, elementRef:ElementRef){ + let nativeElement = elementRef.nativeElement; + } + + ngOnInit(){ + if(this._heroService){ + this.phoneNumber = this._heroService.currentHero.phone; + } + } +} \ No newline at end of file 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..73f76125b7 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/date-logger.service.ts @@ -0,0 +1,9 @@ +// #docregion +import {Injectable} from 'angular2/core'; + +@Injectable() +export class DateLoggerService{ + logInfo(msg:string){ + console.log(new Date().toString() + ` INFO: ${msg}`); + } +} \ No newline at end of file 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..5953c00f7b --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-bios.component.ts @@ -0,0 +1,27 @@ +// #docregion +import {Component} from 'angular2/core'; +import {Bio} from './bio.component'; +import {ContactDetails} from './contact-details.component'; + +@Component({ + template:`
+ + + +
+
+ + + +
+
+ + + +
`, + selector:'hero-bios', + directives:[Bio,ContactDetails] +}) + +export class Heroes{ +} \ No newline at end of file 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..5dde5baafb --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero-of-the-month.component.ts @@ -0,0 +1,28 @@ +// #docregion +import {Component,provide} from 'angular2/core'; +import {LoggerService} from './logger.service'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; +import {DateLoggerService} from './date-logger.service'; +import {RunnersUp} from './runners-up'; +import {runnersUpFactory} from './runners-up-provider.service'; + +@Component({ + selector:'hero-of-the-month', + template:`
Winner: {{_heroOfTheMonth.name}}
+
Reason for award: {{_heroOfTheMonth.description}}
+

Other candidates {{_runnersUp.names}}

`, + + providers:[ + HeroService, + provide(Hero, {useValue:new Hero('Magma','Had a great month!','555-555-5555')}), + provide(LoggerService, {useClass:DateLoggerService}), + provide(RunnersUp, {useFactory:runnersUpFactory, deps:[Hero, HeroService]}) + ] +}) + +export class HeroOfTheMonth{ + constructor(logger:LoggerService, private _heroOfTheMonth:Hero, private _runnersUp:RunnersUp){ + logger.logInfo('starting up'); + } +} \ No newline at end of file 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..a2ef71fb36 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts @@ -0,0 +1,27 @@ +// #docregion +import {Hero} from './hero'; +import {Injectable} from 'angular2/core'; + +@Injectable() +export class HeroService{ + + currentHero:Hero; + + //TODO move to database + private _heros:Array = [new Hero('RubberMan','Hero of many talents', '123-456-7899'), + new Hero('Magma','Hero of all trades', '555-555-5555'), + new Hero('Mr. Nice','The name says it all','111-222-3333')]; + + getHeroById(index:number):Hero{ + if(!this.currentHero){ + let heroes = this.getAllHeroes(); + this.currentHero = heroes[index]; + } + + return this.currentHero; + } + + 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..b4d8a71aeb --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero{ + constructor(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/logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts new file mode 100644 index 0000000000..1f4608933a --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts @@ -0,0 +1,18 @@ +// #docregion +import {Injectable} from 'angular2/core'; + +@Injectable() +export class LoggerService{ + + logInfo(msg:string){ + console.log(`INFO: ${msg}`); + } + + logDebug(msg:string){ + console.log(`DEBUG: ${msg}`); + } + + logError(msg:string){ + console.log(`ERROR: ${msg}`); + } +} \ No newline at end of file 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..73acc083e3 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/main.ts @@ -0,0 +1,9 @@ +// #docregion +import {bootstrap} from 'angular2/platform/browser'; +import {AppComponent} from './app.component'; +import {LoggerService} from './logger.service'; +import {UserContext} from './user-context.service'; +import {UserService} from './user.service'; + +bootstrap(AppComponent, [LoggerService,UserService,UserContext]) + .catch((err:any) => console.error(err)); diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts new file mode 100644 index 0000000000..84b4057a7d --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts @@ -0,0 +1,12 @@ +// #docregion +import {Hero} from './hero'; +import {HeroService} from './hero.service'; +import {RunnersUp} from './runners-up'; + +export const runnersUpFactory = (winner:Hero, heroService:HeroService) => { + let names:string = heroService.getAllHeroes() + .filter((hero) => hero.name !== winner.name) + .map((hero) => hero.name).join(', '); + + return new RunnersUp(names); +}; \ No newline at end of file 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..b66a593402 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts @@ -0,0 +1,11 @@ +// #docregion +import {Injectable} from 'angular2/core'; + +@Injectable() +export class RunnersUp{ + names:string; + + constructor(names:string){ + this.names = names; + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts new file mode 100644 index 0000000000..6ce4946b9b --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts @@ -0,0 +1,22 @@ +// #docregion +import {HeroService} from './hero.service'; +import {Hero} from './hero'; + +export class SortedHeroesBase{ + + sortedHeroes:Array; + + constructor(private heroService:HeroService){ + this.sortedHeroes = heroService.getAllHeroes(); + + this.sortedHeroes.sort((h1,h2) => { + if(h1.name < h2.name){ + return -1; + } + if(h1.name > h2.name){ + return 1 + }; + return 0; + }); + } +} \ No newline at end of file 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..22ac15389e --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes.component.ts @@ -0,0 +1,16 @@ +// #docregion +import {Component} from 'angular2/core'; +import {SortedHeroesBase} from './sorted-heroes-base'; +import {HeroService} from './hero.service'; + +@Component({ + selector:'sorted-heroes', + template:`
{{hero.name}}
`, + providers:[HeroService] +}) + +export class SortedHeroes extends SortedHeroesBase{ + constructor(heroService:HeroService){ + super(heroService); + } +} \ No newline at end of file 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..a0f6aabc51 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/app/user-context.service.ts @@ -0,0 +1,24 @@ +// #docregion +import {UserService} from './user.service'; +import {Injectable} from 'angular2/core'; +import {LoggerService} from './logger.service'; + +@Injectable() +export class UserContext{ + + name:string; + role:string; + loggedInSince:Date; + + constructor(private _userService:UserService, private _loggerService:LoggerService){ + this.loggedInSince = new Date(); + } + + loadUser(userId:number){ + let user = this._userService.getUserById(userId); + this.name = user.name; + this.role = user.role; + + this._loggerService.logDebug('loaded User'); + } +} \ No newline at end of file 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..01c941d326 --- /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..4e41644744 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/index.html @@ -0,0 +1,39 @@ + + + + + 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..be3ca15c42 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/sample.css @@ -0,0 +1,5 @@ +.di-component{ + padding: 10px; + width:300px; + margin-bottom: 10px; +} \ 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..f17315491c 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", + "description": "Using 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..5ccdca4574 --- /dev/null +++ b/public/docs/ts/latest/cookbook/dependency-injection.jade @@ -0,0 +1,143 @@ +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 Dependency](#app-wide-dependency) + + [Nested Dependencies](#nested-dependencies) + + [DI and Inheritance](#di-inheritance) + + [Component Level Dependencies](#component-level-depdendencies) + + [Limit Dependency Lookup](#limit-dependency-lookup) + + [Customizing DI](#customizing-di) + + [Component Element](#component-element) + +:marked + **See the [live example](/resources/live-examples/cb-dependency-injection/ts/plnkr.html)**. + +.l-main-section + + +:marked + ## Application Wide Dependency + Dependencies registered using the `bootstrap` method are global dependencies and can be injected anywhere in the application. + In the following example we have created `LoggerService`, a global logger used to log application events. + ++makeExample('cb-dependency-injection/ts/app/logger.service.ts','','logger.service.ts') + ++makeExample('cb-dependency-injection/ts/app/main.ts','','main.ts') + +:marked + `LoggerService` is now registered with DI and can be injected into `AppComponent`. + ++makeExample('cb-dependency-injection/ts/app/app.component.ts','','app.component.ts') + + +:marked + ## DI and Inheritance + When inheriting a class with DI injected dependencies, all dependencies have to be injected in the sub class and passed down to the base class. Next we will show how to use DI in an inheritance chain. In our example we have created `SortedHeroes` and inherited `SortedHeroesBase` to display a sorted list of heroes. + ++makeExample('cb-dependency-injection/ts/app/sorted-heroes.component.ts','','sorted-heroes.component.ts') + ++makeExample('cb-dependency-injection/ts/app/sorted-heroes-base.ts','','sorted-heroes-base.ts') + +figure.image-display + img(src="/resources/images/cookbooks/dependency-injection/sorted-heroes.png" alt="Sorted Heroes") + +:marked + It may be surprising that we have to inject `HeroService` in `SortedHeroes` since it's really only used by `SortedHeroesBase`, but this is a key requirement of DI. If we move the responsibility of resolving `HeroService` to `SortedHeroesBase` we would lose control over our dependencies in `SortedHeroes` and negate many of the benefits of DI. + + +:marked + ## Nested Dependencies + The benefits of DI become very clear when injecting dependencies with their own dependencies. + Previously we injected `UserContext` in `AppComponent`. `UserContext` has dependencies on both `LoggerService` and `UserService`, but the DI framework knows how to inject these depdendencies when instantiating `UserContext`. All we have to do is put the `@Injectable` decorator on the class to indicate that there are nested dependencies. `@Injectable()` is only required in cases where we have nested dependencies, but we are adding it to all our services for consistency. + + ++makeExample('cb-dependency-injection/ts/app/user-context.service.ts','','user-context.service.ts') + +:marked + We are using UserContext to show information about the current user and the final result looks like this: + +figure.image-display + img(src="/resources/images/cookbooks/dependency-injection/logged-in-user.png" alt="Logged In User") + + +:marked + ## Component Level Dependencies + Application wide dependencies are instantiated once and all consumers share the same instance and state. However, we often need to store state at the component level. In the following example we will show how to register a service at the component level so that we can inject a different instance of the same service in each component instance. + + We will be displaying a list of hero bios using `Heroes`. + ++makeExample('cb-dependency-injection/ts/app/hero-bios.component.ts','','hero-bios.component.ts') + +:marked + `Heroes` contains a list of three instances of `Bio`, a simple component for displaying the bio for a given hero. + ++makeExample('cb-dependency-injection/ts/app/bio.component.ts','','bio.component.ts') + +:marked + `Bio` uses `HeroService` to load the specific hero to display. + ++makeExample('cb-dependency-injection/ts/app/hero.service.ts','','hero.service.ts') + +:marked + `HeroService` is caching the currently loaded hero, but each `Bio` instance needs to load a different hero. Clearly, a shared instance of `HeroService` will not work here. Instead we need to tell DI to give us a new instance of `HeroService` for each instance of `Bio` by registering `HersoService` in the `provider` array. + +figure.image-display + img(src="/resources/images/cookbooks/dependency-injection/hero-bios.png" alt="Bios") + + +:marked + ## Limit Dependency Lookup + As we have seen, dependencies can be registered at any level in the component hierarchy. Anytime we request a dependency, Angular DI will walk up the injector tree until the first suitable provider is found somewhere in the chain. Most of the time this is the behavior we want, but there are times when we want to limit the lookup path. Angular DI provides the `@Host` decorator to make sure the dependency is resolved based on the provider defined in the host component. + + `@Host` can be used in tandem with `@Optional` to ensure that dependencies don't resolve unless registered by the host specifically. + + In our previoius sample we displayed hero bios where each instance of `Bio` required a separate instance of `HeroService`. We will now extend this sample by defining `ContactDetails` as a child component of `Bio`. Given this dependency it is important that `ContactDetails` uses the same instance of `HeroService` as its `Bio` host. We are adding `@Host` to ensure that. + ++makeExample('cb-dependency-injection/ts/app/contact-details.component.ts','','contact-details.component.ts') + + +:marked + ## Customizing DI + In the following sample we will show how to configure Angular's DI framework and control how instances are resolved. + + A typical use case for wanting to customize DI is mocking. Mocks can be created for unit tests, but also to work ahead on a feature while we wait for other teams to build the services our components depend on. In the following example we will be building `HeroOfTheMonth` where we customize the behavior of some of the services we introduced in previous sections. + ++makeExample('cb-dependency-injection/ts/app/hero-of-the-month.component.ts','','hero-of-the-month.component.ts') + +:marked + ***useValue*** + + We are using `useValue` to always return a specific hero whenever a `Hero` is injected. + + ***useClass*** + + Previsoulsy we created `LoggerService`, but we are experimenting with a new logger, so in `HeroOfTheMonth` we want to use `useClass` to return an instance of `DateLoggerService` whenever a `LoggerService` is requested. + + ***useFactory*** + + Along side the lucky hero of the month we also want to recognize the runners-up for the month. We are injecting a `RunnersUp` object with a list of heroes who came close to winning the prestigious award. This list needs to be created based on some internal logic, so we have used `useFactory` to create a factory to create the list. + ++makeExample('cb-dependency-injection/ts/app/runners-up-provider.service.ts','','runners-up-provider.service.ts') + ++makeExample('cb-dependency-injection/ts/app/runners-up.ts','','runners-up.ts') + +figure.image-display + img(src="/resources/images/cookbooks/dependency-injection/hero-of-month.png" alt="Hero of the month") + + +:marked + ## Component Element + + On occasion we might need to access a component's underlying host element. Direct DOM access should generally be avoided, but looking back at our `ContactDetails` sample, we are using DI to inject `ElementRef` to get access to the underlying component element. \ No newline at end of file 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 0000000000000000000000000000000000000000..611e61261b512228ebdd7d7ac7e7c3f4a226c2d2 GIT binary patch literal 23420 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..84067037a0d9db25d94684f3d344bf0499963c88 GIT binary patch literal 16797 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4a43da33c9d70ba785b998a2c6076520ed2fc656 GIT binary patch literal 8274 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 c0O1A2J;Dje0Y6~z>% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6adfeae498d09238f20f7c32eb6906a2b30abd5c GIT binary patch literal 8187 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 JtX6 Date: Tue, 29 Mar 2016 18:22:55 -0700 Subject: [PATCH 2/3] docs(cb-di): ward's tweaks to Tors cookbook --- gulpfile.js | 1 + public/docs/_examples/.gitignore | 1 + .../ts/app/highlight.directive.2.ts | 6 +- .../ts/app/highlight.directive.ts | 21 +- .../cb-dependency-injection/e2e-spec.js | 81 +- .../ts/app/app.component.html | 39 + .../ts/app/app.component.ts | 75 +- .../ts/app/bio.component.ts | 27 - .../ts/app/contact-details.component.ts | 23 - .../ts/app/date-logger.service.ts | 40 +- .../ts/app/hero-bio.component.ts | 29 + .../ts/app/hero-bios.component.ts | 67 +- .../ts/app/hero-cache.service.ts | 19 + .../ts/app/hero-contact.component.ts | 39 + .../ts/app/hero-data.ts | 14 + .../ts/app/hero-of-the-month.component.ts | 89 +- .../ts/app/hero.service.ts | 31 +- .../cb-dependency-injection/ts/app/hero.ts | 6 +- .../ts/app/highlight.directive.ts | 28 + .../ts/app/logger.service.ts | 24 +- .../cb-dependency-injection/ts/app/main.ts | 32 +- .../ts/app/parent-finder.component.ts | 227 +++++ .../ts/app/runners-up-provider.service.ts | 12 - .../ts/app/runners-up.ts | 32 +- .../ts/app/sorted-heroes-base.ts | 22 - .../ts/app/sorted-heroes.component.ts | 53 +- .../ts/app/user-context.service.ts | 24 +- .../ts/app/user.service.ts | 6 +- .../cb-dependency-injection/ts/index.html | 7 +- .../cb-dependency-injection/ts/sample.css | 21 + public/docs/_examples/package.json | 2 +- .../_examples/quickstart/ts/package.1.json | 2 +- public/docs/_examples/tslint.json | 93 ++ public/docs/dart/latest/cookbook/_data.json | 8 +- .../latest/cookbook/dependency-injection.jade | 1 + public/docs/js/latest/cookbook/_data.json | 10 +- .../latest/cookbook/dependency-injection.jade | 1 + public/docs/ts/latest/cookbook/_data.json | 2 +- .../latest/cookbook/dependency-injection.jade | 909 ++++++++++++++++-- .../ts/latest/guide/attribute-directives.jade | 9 +- .../ts/latest/guide/dependency-injection.jade | 7 +- public/docs/ts/latest/guide/pipes.jade | 3 +- public/docs/ts/latest/guide/router.jade | 3 +- .../cookbooks/dependency-injection/alex.png | Bin 0 -> 20533 bytes .../cookbooks/dependency-injection/alice.png | Bin 0 -> 46898 bytes .../date-logger-entry.png | Bin 0 -> 10087 bytes .../hero-bio-and-content.png | Bin 0 -> 9554 bytes .../hero-bio-contact-no-host.png | Bin 0 -> 9975 bytes .../hero-bios-and-contacts.png | Bin 0 -> 29600 bytes .../dependency-injection/hero-bios.png | Bin 23420 -> 16720 bytes .../dependency-injection/hero-of-month.png | Bin 16797 -> 26538 bytes .../dependency-injection/highlight.png | Bin 0 -> 16043 bytes .../minimal-logger-intellisense.png | Bin 0 -> 15037 bytes 53 files changed, 1794 insertions(+), 352 deletions(-) create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/app.component.html delete mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts delete mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-bio.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-cache.service.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-contact.component.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/hero-data.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/highlight.directive.ts create mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/parent-finder.component.ts delete mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts delete mode 100644 public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts create mode 100644 public/docs/_examples/tslint.json create mode 100644 public/docs/dart/latest/cookbook/dependency-injection.jade create mode 100644 public/docs/js/latest/cookbook/dependency-injection.jade create mode 100644 public/resources/images/cookbooks/dependency-injection/alex.png create mode 100644 public/resources/images/cookbooks/dependency-injection/alice.png create mode 100644 public/resources/images/cookbooks/dependency-injection/date-logger-entry.png create mode 100644 public/resources/images/cookbooks/dependency-injection/hero-bio-and-content.png create mode 100644 public/resources/images/cookbooks/dependency-injection/hero-bio-contact-no-host.png create mode 100644 public/resources/images/cookbooks/dependency-injection/hero-bios-and-contacts.png create mode 100644 public/resources/images/cookbooks/dependency-injection/highlight.png create mode 100644 public/resources/images/cookbooks/dependency-injection/minimal-logger-intellisense.png diff --git a/gulpfile.js b/gulpfile.js index 774c1c4381..2ab015cdc7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -74,6 +74,7 @@ var _exampleBoilerplateFiles = [ 'package.json', 'styles.css', 'tsconfig.json', + 'tslint.json', 'typings.json' ]; diff --git a/public/docs/_examples/.gitignore b/public/docs/_examples/.gitignore index 7a15251d4f..7e2250f509 100644 --- a/public/docs/_examples/.gitignore +++ b/public/docs/_examples/.gitignore @@ -6,5 +6,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 index 02237faab6..aeb0e68b17 100644 --- a/public/docs/_examples/cb-dependency-injection/e2e-spec.js +++ b/public/docs/_examples/cb-dependency-injection/e2e-spec.js @@ -1,41 +1,98 @@ -describe('Dependency Injection', function () { +describe('Dependency Injection Cookbook', function () { beforeAll(function () { browser.get(''); }); - it('should render DI samples', function () { + 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)"; - var runnersUp = element.all(by.xpath('//h4[text()="Other candidates RubberMan, Mr. Nice"]')).get(0); - expect(runnersUp).toBeDefined(); + 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/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 index fb00ccc80d..2e4485f665 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/app.component.ts @@ -1,47 +1,46 @@ // #docregion -import {Component,OnInit} from 'angular2/core'; -import {LoggerService} from './logger.service'; -import {UserContext} from './user-context.service'; -import {Heroes} from './hero-bios.component'; -import {SortedHeroes} from './sorted-heroes.component'; -import {HeroOfTheMonth} from './hero-of-the-month.component'; +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', - directives:[Heroes,SortedHeroes,HeroOfTheMonth], - template: - `

DI Components

-
-

Logged in user

-
Name: {{_userContext.name}}
-
Role: {{_userContext.role}}
-
- -
-

Sorted Heroes

- -
- -
-

Hero of the month

- -
- -
-

Hero Bios

- -
` + templateUrl:'app/app.component.html', + directives: DIRECTIVES, +// #docregion providers + providers: [LoggerService, UserContextService, UserService] +// #enddocregion providers }) +export class AppComponent { +// #enddocregion import-services -export class AppComponent implements OnInit { - private userId:number = 1; - - constructor(private _logger:LoggerService, private _userContext:UserContext){ - this._userContext.loadUser(this.userId); - } - - ngOnInit(){ - this._logger.logInfo('AppComponent initialized'); + + // #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/bio.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts deleted file mode 100644 index b993d57ff5..0000000000 --- a/public/docs/_examples/cb-dependency-injection/ts/app/bio.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -// #docregion -import {Component,Input,OnInit} from 'angular2/core'; -import {Hero} from './hero'; -import {HeroService} from './hero.service'; - -@Component({ - selector:'bio', - template:`

{{hero.name}}

-
- - -
`, - providers:[HeroService] -}) - -export class Bio implements OnInit{ - - @Input() heroIndex:number; - private hero:Hero; - - constructor(private _heroService:HeroService){ - } - - ngOnInit(){ - this.hero = this._heroService.getHeroById(this.heroIndex); - } -} \ No newline at end of file diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts b/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts deleted file mode 100644 index d8f8331636..0000000000 --- a/public/docs/_examples/cb-dependency-injection/ts/app/contact-details.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -// #docregion -import {Component, Host, Optional, OnInit, ElementRef} from 'angular2/core'; - -import {HeroService} from './hero.service'; - -@Component({ - selector:'contact-details', - template:'
Phone #: {{phoneNumber}}
' -}) - -export class ContactDetails implements OnInit{ - - private phoneNumber:string; - constructor(@Optional() @Host() private _heroService:HeroService, elementRef:ElementRef){ - let nativeElement = elementRef.nativeElement; - } - - ngOnInit(){ - if(this._heroService){ - this.phoneNumber = this._heroService.currentHero.phone; - } - } -} \ No newline at end of file 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 index 73f76125b7..125ba37c2b 100644 --- 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 @@ -1,9 +1,37 @@ +/* tslint:disable:one-line:check-open-brace*/ // #docregion -import {Injectable} from 'angular2/core'; +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() -export class DateLoggerService{ - logInfo(msg:string){ - console.log(new Date().toString() + ` INFO: ${msg}`); - } -} \ No newline at end of file +// #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 index 5953c00f7b..9d14daf648 100644 --- 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 @@ -1,27 +1,52 @@ +// #docplaster // #docregion -import {Component} from 'angular2/core'; -import {Bio} from './bio.component'; -import {ContactDetails} from './contact-details.component'; +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({ - template:`
- - - -
-
- - - -
-
- - - -
`, selector:'hero-bios', - directives:[Bio,ContactDetails] + 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 -export class Heroes{ -} \ No newline at end of file +//////// 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 index 5dde5baafb..02dcae4824 100644 --- 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 @@ -1,28 +1,75 @@ -// #docregion -import {Component,provide} from 'angular2/core'; -import {LoggerService} from './logger.service'; -import {Hero} from './hero'; -import {HeroService} from './hero.service'; -import {DateLoggerService} from './date-logger.service'; -import {RunnersUp} from './runners-up'; -import {runnersUpFactory} from './runners-up-provider.service'; +/* 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:`
Winner: {{_heroOfTheMonth.name}}
-
Reason for award: {{_heroOfTheMonth.description}}
-

Other candidates {{_runnersUp.names}}

`, - - providers:[ - HeroService, - provide(Hero, {useValue:new Hero('Magma','Had a great month!','555-555-5555')}), - provide(LoggerService, {useClass:DateLoggerService}), - provide(RunnersUp, {useFactory:runnersUpFactory, deps:[Hero, HeroService]}) + 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[] = []; -export class HeroOfTheMonth{ - constructor(logger:LoggerService, private _heroOfTheMonth:Hero, private _runnersUp:RunnersUp){ +// #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'); } -} \ No newline at end of file +} +// #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 index a2ef71fb36..274753ae8d 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.service.ts @@ -1,26 +1,21 @@ // #docregion -import {Hero} from './hero'; import {Injectable} from 'angular2/core'; +import {Hero} from './hero'; @Injectable() -export class HeroService{ - - currentHero:Hero; - +export class HeroService { + //TODO move to database - private _heros:Array = [new Hero('RubberMan','Hero of many talents', '123-456-7899'), - new Hero('Magma','Hero of all trades', '555-555-5555'), - new Hero('Mr. Nice','The name says it all','111-222-3333')]; - - getHeroById(index:number):Hero{ - if(!this.currentHero){ - let heroes = this.getAllHeroes(); - this.currentHero = heroes[index]; - } - - return this.currentHero; - } - + 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; } diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts b/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts index b4d8a71aeb..51ce8ebbff 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/hero.ts @@ -1,5 +1,9 @@ // #docregion export class Hero{ - constructor(public name:string, public description:string, public phone:string){ + 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 index 1f4608933a..ecc75350e3 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/logger.service.ts @@ -2,17 +2,15 @@ import {Injectable} from 'angular2/core'; @Injectable() -export class LoggerService{ - - logInfo(msg:string){ - console.log(`INFO: ${msg}`); - } - - logDebug(msg:string){ - console.log(`DEBUG: ${msg}`); - } - - logError(msg:string){ - console.log(`ERROR: ${msg}`); +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); } -} \ No newline at end of file +} diff --git a/public/docs/_examples/cb-dependency-injection/ts/app/main.ts b/public/docs/_examples/cb-dependency-injection/ts/app/main.ts index 73acc083e3..b1e35e2a67 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/main.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/main.ts @@ -1,9 +1,25 @@ // #docregion -import {bootstrap} from 'angular2/platform/browser'; -import {AppComponent} from './app.component'; -import {LoggerService} from './logger.service'; -import {UserContext} from './user-context.service'; -import {UserService} from './user.service'; - -bootstrap(AppComponent, [LoggerService,UserService,UserContext]) - .catch((err:any) => console.error(err)); +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-provider.service.ts b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts deleted file mode 100644 index 84b4057a7d..0000000000 --- a/public/docs/_examples/cb-dependency-injection/ts/app/runners-up-provider.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -// #docregion -import {Hero} from './hero'; -import {HeroService} from './hero.service'; -import {RunnersUp} from './runners-up'; - -export const runnersUpFactory = (winner:Hero, heroService:HeroService) => { - let names:string = heroService.getAllHeroes() - .filter((hero) => hero.name !== winner.name) - .map((hero) => hero.name).join(', '); - - return new RunnersUp(names); -}; \ No newline at end of file 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 index b66a593402..a2d79df35e 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/runners-up.ts @@ -1,11 +1,25 @@ +// #docplaster // #docregion -import {Injectable} from 'angular2/core'; +import {OpaqueToken} from 'angular2/core'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; -@Injectable() -export class RunnersUp{ - names:string; - - constructor(names:string){ - this.names = names; - } -} \ No newline at end of file +// #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-base.ts b/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts deleted file mode 100644 index 6ce4946b9b..0000000000 --- a/public/docs/_examples/cb-dependency-injection/ts/app/sorted-heroes-base.ts +++ /dev/null @@ -1,22 +0,0 @@ -// #docregion -import {HeroService} from './hero.service'; -import {Hero} from './hero'; - -export class SortedHeroesBase{ - - sortedHeroes:Array; - - constructor(private heroService:HeroService){ - this.sortedHeroes = heroService.getAllHeroes(); - - this.sortedHeroes.sort((h1,h2) => { - if(h1.name < h2.name){ - return -1; - } - if(h1.name > h2.name){ - return 1 - }; - return 0; - }); - } -} \ No newline at end of file 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 index 22ac15389e..2a45367044 100644 --- 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 @@ -1,16 +1,51 @@ +// #docplaster // #docregion -import {Component} from 'angular2/core'; -import {SortedHeroesBase} from './sorted-heroes-base'; -import {HeroService} from './hero.service'; +import {Component, OnInit} from 'angular2/core'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; +/////// HeroesBaseComponent ///// +// #docregion heroes-base, injection @Component({ - selector:'sorted-heroes', - template:`
{{hero.name}}
`, - providers:[HeroService] + selector: 'unsorted-heroes', + template: `
{{hero.name}}
`, + providers: [HeroService] }) +export class HeroesBaseComponent implements OnInit { + constructor(private _heroService: HeroService) { } +// #enddocregion injection -export class SortedHeroes extends SortedHeroesBase{ - constructor(heroService:HeroService){ + 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); } -} \ No newline at end of file + + 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 index a0f6aabc51..b241675d31 100644 --- 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 @@ -1,24 +1,32 @@ +// #docplaster // #docregion -import {UserService} from './user.service'; import {Injectable} from 'angular2/core'; import {LoggerService} from './logger.service'; +import {UserService} from './user.service'; +// #docregion injectables, injectable @Injectable() -export class UserContext{ - +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.role = user.role; + this._loggerService.logDebug('loaded User'); } -} \ No newline at end of file +// #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 index 01c941d326..f2d44d7a26 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts +++ b/public/docs/_examples/cb-dependency-injection/ts/app/user.service.ts @@ -2,9 +2,9 @@ import {Injectable} from 'angular2/core'; @Injectable() -export class UserService{ - +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/index.html b/public/docs/_examples/cb-dependency-injection/ts/index.html index 4e41644744..66db40eb30 100644 --- a/public/docs/_examples/cb-dependency-injection/ts/index.html +++ b/public/docs/_examples/cb-dependency-injection/ts/index.html @@ -12,12 +12,17 @@ - + + + + + +