From ab5390a51ec15769a70211c99434db2cff95956c Mon Sep 17 00:00:00 2001 From: John Papa Date: Sun, 15 Nov 2015 18:04:43 -0800 Subject: [PATCH] docs(tutorial): combines all 4 sections + revisions/updates to a.53, hides 3&4 closes #488 ToH History (oldest-to-latest): ---------------------------- created code example/snippets files for use with +makeExample, replace usage of "pre.prettyprint.lang-bash" with this: code-example(format="." language="bash"). fixed spelling errors in examples file path used by +makeExample changed usage of "code-example" to "+makeExample" adding code example/snippets files used in toh 1 fixed example file paths, replaced "pre.prettyprint.lang-bash" with "code-example. " (docs) toh-pt3 initial state created code examples for display in jade, starting conversion of Google doc and trying +makeExample rendering all text copied from doc to jade, still some styling and formatting to perform completed conversion and styling, moved toh3 example files to "tutorial" folder under _examples created specific code example files for chapter toh 3 and re-pathed references in +makeExample minor edit docs) toh combined - initial combined commit updated ToH for a.52 tons of changes, including de-kebab-ing, removed src folder, updated tsconfig too fixing snippets using incorrect ending input tag using inline html and css for the app.component. ToH Part 1 Code: updated the imports, removed obsolete directive delcarations ToH Code Part 1: updated to use imports, interface. will hit others soon toh part 1: ngModel fix toh part1: removed obsolete story that referred to how we used to have to import and declare all directives we used. yay! ToH Part 1: updated to use `boot.ts` and `app.component.ts`. this affected the partials, snippets, and the story. toh part 1: using `npm run go` toh parts 1 -4: modified all places to use `npm run go` toh part 1: refactor for jade toh part 1: fixing the code samples toh part 2: seeping through the story toh part 2: fixing snippets. toh part 2: replaced ngClass with class.selected toh part 2: removed whitespace toh part 2: added final state to the code toh: fixing paths toh part 4: fixing src/app path to app toh part 3: fixing folder path toh part 2: fixed typo toh part 2: typo on ngModel toh part 2: added ngif toh part 2: removed old hero property. moved the details lower, where we need it toh index: updated hero list image to show consistent styling as the other images here QS spelling error (targes -> targets) tweeks: space and ngIF --- public/docs/_examples/.gitignore | 3 +- public/docs/_examples/jsconfig.json | 2 +- public/docs/_examples/protractor-conf.js | 2 +- .../ts/app/app.component.snippets.pt1.ts | 53 ++ .../_examples/toh-1/ts/app/app.component.ts | 29 + public/docs/_examples/toh-1/ts/app/boot.ts | 6 + .../ts/app/app.component.snippets.pt2.ts | 99 +++ .../_examples/toh-2/ts/app/app.component.ts | 74 +++ public/docs/_examples/toh-2/ts/app/boot.ts | 6 + .../ts/app/hero-detail.component.pt3.html | 14 + .../toh-3/ts/app/hero-detail.snippets.pt3.ts | 34 + .../_examples/toh-3/ts/app/mock-heroes.ts | 15 + .../toh-4/ts/app/app.component.pt4.ts | 111 ++++ .../_examples/toh-4/ts/app/bootstrap.pt4.ts | 13 + .../toh-4/ts/app/hero.service.pt4.ts | 8 + .../_examples/toh-4/ts/app/index.pt4.html | 186 ++++++ .../toh-4/ts/app/route.config.pt4.ts | 50 ++ public/docs/_examples/tsd.json | 24 - public/docs/_examples/tutorial/ts/.gitignore | 6 + .../tutorial/ts/app/app.component.ts | 31 + public/docs/_examples/tutorial/ts/app/boot.ts | 9 + .../tutorial/ts/app/dashboard.component.css | 37 ++ .../tutorial/ts/app/dashboard.component.html | 8 + .../tutorial/ts/app/dashboard.component.ts | 23 + .../ts/app/hero-detail.component.html | 10 + .../tutorial/ts/app/hero-detail.component.ts | 28 + .../_examples/tutorial/ts/app/hero.service.ts | 14 + public/docs/_examples/tutorial/ts/app/hero.ts | 4 + .../tutorial/ts/app/heroes.component.css | 17 + .../tutorial/ts/app/heroes.component.html | 14 + .../tutorial/ts/app/heroes.component.ts | 37 ++ .../_examples/tutorial/ts/app/mock-heroes.ts | 14 + .../_examples/tutorial/ts/example-config.json | 0 public/docs/_examples/tutorial/ts/index.html | 22 + public/docs/_examples/tutorial/ts/plnkr.json | 8 + public/docs/_examples/tutorial/ts/styles.css | 4 + public/docs/ts/latest/quickstart.jade | 5 +- .../tutorial/dependency-injection.1.jade | 623 ++++++++++++++++++ .../latest/tutorial/dependency-injection.jade | 623 ++++++++++++++++++ public/docs/ts/latest/tutorial/index.jade | 6 +- public/docs/ts/latest/tutorial/toh-pt1.jade | 313 +++------ public/docs/ts/latest/tutorial/toh-pt2.jade | 438 +++++------- public/docs/ts/latest/tutorial/toh-pt3.jade | 363 ++++++++++ public/docs/ts/latest/tutorial/toh-pt4.jade | 473 +++++++++++++ .../devguide/toh/dashboard-top-heroes.png | Bin 0 -> 17689 bytes .../images/devguide/toh/heroes-list-3.png | Bin 0 -> 28710 bytes 46 files changed, 3314 insertions(+), 545 deletions(-) create mode 100644 public/docs/_examples/toh-1/ts/app/app.component.snippets.pt1.ts create mode 100644 public/docs/_examples/toh-1/ts/app/app.component.ts create mode 100644 public/docs/_examples/toh-1/ts/app/boot.ts create mode 100644 public/docs/_examples/toh-2/ts/app/app.component.snippets.pt2.ts create mode 100644 public/docs/_examples/toh-2/ts/app/app.component.ts create mode 100644 public/docs/_examples/toh-2/ts/app/boot.ts create mode 100644 public/docs/_examples/toh-3/ts/app/hero-detail.component.pt3.html create mode 100644 public/docs/_examples/toh-3/ts/app/hero-detail.snippets.pt3.ts create mode 100644 public/docs/_examples/toh-3/ts/app/mock-heroes.ts create mode 100644 public/docs/_examples/toh-4/ts/app/app.component.pt4.ts create mode 100644 public/docs/_examples/toh-4/ts/app/bootstrap.pt4.ts create mode 100644 public/docs/_examples/toh-4/ts/app/hero.service.pt4.ts create mode 100644 public/docs/_examples/toh-4/ts/app/index.pt4.html create mode 100644 public/docs/_examples/toh-4/ts/app/route.config.pt4.ts delete mode 100644 public/docs/_examples/tsd.json create mode 100644 public/docs/_examples/tutorial/ts/.gitignore create mode 100644 public/docs/_examples/tutorial/ts/app/app.component.ts create mode 100644 public/docs/_examples/tutorial/ts/app/boot.ts create mode 100644 public/docs/_examples/tutorial/ts/app/dashboard.component.css create mode 100644 public/docs/_examples/tutorial/ts/app/dashboard.component.html create mode 100644 public/docs/_examples/tutorial/ts/app/dashboard.component.ts create mode 100644 public/docs/_examples/tutorial/ts/app/hero-detail.component.html create mode 100644 public/docs/_examples/tutorial/ts/app/hero-detail.component.ts create mode 100644 public/docs/_examples/tutorial/ts/app/hero.service.ts create mode 100644 public/docs/_examples/tutorial/ts/app/hero.ts create mode 100644 public/docs/_examples/tutorial/ts/app/heroes.component.css create mode 100644 public/docs/_examples/tutorial/ts/app/heroes.component.html create mode 100644 public/docs/_examples/tutorial/ts/app/heroes.component.ts create mode 100644 public/docs/_examples/tutorial/ts/app/mock-heroes.ts create mode 100644 public/docs/_examples/tutorial/ts/example-config.json create mode 100644 public/docs/_examples/tutorial/ts/index.html create mode 100644 public/docs/_examples/tutorial/ts/plnkr.json create mode 100644 public/docs/_examples/tutorial/ts/styles.css create mode 100644 public/docs/ts/latest/tutorial/dependency-injection.1.jade create mode 100644 public/docs/ts/latest/tutorial/dependency-injection.jade create mode 100644 public/docs/ts/latest/tutorial/toh-pt3.jade create mode 100644 public/docs/ts/latest/tutorial/toh-pt4.jade create mode 100644 public/resources/images/devguide/toh/dashboard-top-heroes.png create mode 100644 public/resources/images/devguide/toh/heroes-list-3.png diff --git a/public/docs/_examples/.gitignore b/public/docs/_examples/.gitignore index 8cf3677823..02d898bf04 100644 --- a/public/docs/_examples/.gitignore +++ b/public/docs/_examples/.gitignore @@ -3,4 +3,5 @@ typings package.json karma.conf.js karma-test-shim.js -tsconfig.json \ No newline at end of file +tsconfig.json +npm-debug*.log diff --git a/public/docs/_examples/jsconfig.json b/public/docs/_examples/jsconfig.json index b3a1252357..82750484d1 100644 --- a/public/docs/_examples/jsconfig.json +++ b/public/docs/_examples/jsconfig.json @@ -3,4 +3,4 @@ "target": "ES5", "module": "commonjs" } -} \ No newline at end of file +} diff --git a/public/docs/_examples/protractor-conf.js b/public/docs/_examples/protractor-conf.js index 6a183ce67b..12dd89ab9f 100644 --- a/public/docs/_examples/protractor-conf.js +++ b/public/docs/_examples/protractor-conf.js @@ -21,4 +21,4 @@ function patchProtractorWait(browser) { browser.sleep(sleepInterval); return result; } -} \ No newline at end of file +} diff --git a/public/docs/_examples/toh-1/ts/app/app.component.snippets.pt1.ts b/public/docs/_examples/toh-1/ts/app/app.component.snippets.pt1.ts new file mode 100644 index 0000000000..9d7d15f08e --- /dev/null +++ b/public/docs/_examples/toh-1/ts/app/app.component.snippets.pt1.ts @@ -0,0 +1,53 @@ +// #docregion show-hero +template: '

{{title}}

{{hero}} details!

' +// #enddocregion show-hero + +// #docregion show-hero-2 +template: '

{{title}}

{{hero.name}} details!

' +// #enddocregion show-hero-2 + +// #docregion show-hero-properties +template: '

{{title}}

{{hero.name}} details!

{{hero.id}}
{{hero.name}}
' +// #enddocregion show-hero-properties + +// #docregion multi-line-strings +template:` +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
{{hero.name}}
+ ` +// #enddocregion multi-line-strings + +// #docregion editing-Hero +template:` +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
+ +
+
+ ` +// #enddocregion editing-Hero + +// #docregion app-component-1 + export class AppComponent { + public title = 'Tour of Heroes'; + public hero = 'Windstorm'; + } +// #enddocregion app-component-1 + +// #docregion hero-interface-1 + interface Hero { + id: number; + name: string; + } +// #enddocregion hero-interface-1 + +// #docregion hero-property-1 + public hero: Hero = { + id: 1, + name: 'Windstorm' + }; +// #enddocregion hero-property-1 diff --git a/public/docs/_examples/toh-1/ts/app/app.component.ts b/public/docs/_examples/toh-1/ts/app/app.component.ts new file mode 100644 index 0000000000..bd98c90b1e --- /dev/null +++ b/public/docs/_examples/toh-1/ts/app/app.component.ts @@ -0,0 +1,29 @@ +// #docregion pt1 +import {Component} from 'angular2/core'; + +interface Hero { + id: number; + name: string; +} + +@Component({ + selector: 'my-app', + template:` +

{{title}}

+

{{hero.name}} details!

+
{{hero.id}}
+
+ +
+
+ ` +}) +export class AppComponent { + public title = 'Tour of Heroes'; + public hero: Hero = { + id: 1, + name: 'Windstorm' + }; +} + +// #enddocregion pt1 diff --git a/public/docs/_examples/toh-1/ts/app/boot.ts b/public/docs/_examples/toh-1/ts/app/boot.ts new file mode 100644 index 0000000000..a5cf62c0fd --- /dev/null +++ b/public/docs/_examples/toh-1/ts/app/boot.ts @@ -0,0 +1,6 @@ +// #docregion pt1 +import {bootstrap} from 'angular2/platform/browser'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent); +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/toh-2/ts/app/app.component.snippets.pt2.ts b/public/docs/_examples/toh-2/ts/app/app.component.snippets.pt2.ts new file mode 100644 index 0000000000..cb4c3335a1 --- /dev/null +++ b/public/docs/_examples/toh-2/ts/app/app.component.snippets.pt2.ts @@ -0,0 +1,99 @@ +// #docregion ng-for +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion ng-for + +// #docregion heroes-styled +

    My Heroes

    + +// #enddocregion heroes-styled + +// #docregion selectedHero-click +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion selectedHero-click + +// #docregion selectedHero-details +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +// #enddocregion selectedHero-details + +// #docregion ng-if +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    +// #enddocregion ng-if + +// #docregion hero-array-1 +public heroes = HEROES; +// #enddocregion hero-array-1 + +// #docregion heroes-template-1 +

    My Heroes

    + +// #enddocregion heroes-template-1 + +// #docregion heroes-ngfor-1 +
  • +// #enddocregion heroes-ngfor-1 + +// #docregion styles-1 +styles:[` + .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} + + .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } + + .heroes li:hover {color: #369; background-color: #EEE; left: .2em;} + + .heroes .badge { + font-size: small; + color: white; + padding: 0.1em 0.7em; + background-color: #369; + line-height: 1em; + position: relative; + left: -1px; + top: -1px; + } + .selected { background-color: #EEE; color: #369; } +`] +// #enddocregion styles-1 + +// #docregion selected-hero-1 +public selectedHero: Hero; +// #enddocregion selected-hero-1 + +// #docregion on-select-1 +onSelect(hero: Hero) { this.selectedHero = hero; } +// #enddocregion on-select-1 + +// #docregion class-selected-1 +[class.selected]="hero === selectedHero" +// #enddocregion class-selected-1 + +// #docregion class-selected-2 +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion class-selected-2 diff --git a/public/docs/_examples/toh-2/ts/app/app.component.ts b/public/docs/_examples/toh-2/ts/app/app.component.ts new file mode 100644 index 0000000000..7c3daf0b2b --- /dev/null +++ b/public/docs/_examples/toh-2/ts/app/app.component.ts @@ -0,0 +1,74 @@ +// #docregion pt2 +import {Component} from 'angular2/core'; + +interface Hero { + id: number; + name: string; +} + +@Component({ + selector: 'my-app', + template:` +

    {{title}}

    +

    My Heroes

    + +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    + `, + styles:[` + .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} + + .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } + + .heroes li:hover {color: #369; background-color: #EEE; left: .2em;} + + .heroes .badge { + font-size: small; + color: white; + padding: 0.1em 0.7em; + background-color: #369; + line-height: 1em; + position: relative; + left: -1px; + top: -1px; + } + .selected { background-color: #EEE; color: #369; } + `] +}) +export class AppComponent { + public title = 'Tour of Heroes'; + public heroes = HEROES; + public selectedHero: Hero; + + onSelect(hero: Hero) { this.selectedHero = hero; } +} +// #enddocregion pt2 + +// #docregion hero-array +var HEROES: Hero[] = [ + { "id": 11, "name": "Mr. Nice" }, + { "id": 12, "name": "Narco" }, + { "id": 13, "name": "Bombasto" }, + { "id": 14, "name": "Celeritas" }, + { "id": 15, "name": "Magneta" }, + { "id": 16, "name": "RubberMan" }, + { "id": 17, "name": "Dynama" }, + { "id": 18, "name": "Dr IQ" }, + { "id": 19, "name": "Magma" }, + { "id": 20, "name": "Tornado" } +]; +// #enddocregion hero-array + +// #enddocregion pt2 diff --git a/public/docs/_examples/toh-2/ts/app/boot.ts b/public/docs/_examples/toh-2/ts/app/boot.ts new file mode 100644 index 0000000000..a5cf62c0fd --- /dev/null +++ b/public/docs/_examples/toh-2/ts/app/boot.ts @@ -0,0 +1,6 @@ +// #docregion pt1 +import {bootstrap} from 'angular2/platform/browser'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent); +// #enddocregion pt1 \ No newline at end of file diff --git a/public/docs/_examples/toh-3/ts/app/hero-detail.component.pt3.html b/public/docs/_examples/toh-3/ts/app/hero-detail.component.pt3.html new file mode 100644 index 0000000000..0c8cf753b5 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/app/hero-detail.component.pt3.html @@ -0,0 +1,14 @@ + + template: ` +

    {{title}}

    + +

    My Heroes

    + + + `, diff --git a/public/docs/_examples/toh-3/ts/app/hero-detail.snippets.pt3.ts b/public/docs/_examples/toh-3/ts/app/hero-detail.snippets.pt3.ts new file mode 100644 index 0000000000..0b6c6601cc --- /dev/null +++ b/public/docs/_examples/toh-3/ts/app/hero-detail.snippets.pt3.ts @@ -0,0 +1,34 @@ +// #docregion template +@Component({ + selector: 'my-hero-detail', + template: ` +
    +

    {{selected.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + `, + directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] + }) +// #enddocregion template + +// #docregion inputs +@Component({ + selector: 'my-hero-detail', + template: ` +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + `, + directives: [CORE_DIRECTIVES, FORM_DIRECTIVES], + inputs: ['hero'] + }) + // #enddocregion inputs \ No newline at end of file diff --git a/public/docs/_examples/toh-3/ts/app/mock-heroes.ts b/public/docs/_examples/toh-3/ts/app/mock-heroes.ts new file mode 100644 index 0000000000..2d8668e008 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/app/mock-heroes.ts @@ -0,0 +1,15 @@ +import { Hero } from './hero'; +// #docregion mocking-heroes +export var HEROES: Hero[] = [ + {"id": 11, "name": "Mr. Nice"}, + {"id": 12, "name": "Narco"}, + {"id": 13, "name": "Bombasto"}, + {"id": 14, "name": "Celeritas"}, + {"id": 15, "name": "Magneta"}, + {"id": 16, "name": "RubberMan"}, + {"id": 17, "name": "Dynama"}, + {"id": 18, "name": "Dr IQ"}, + {"id": 19, "name": "Magma"}, + {"id": 20, "name": "Tornado"} +]; +// #enddocregion mocking-heroes \ No newline at end of file diff --git a/public/docs/_examples/toh-4/ts/app/app.component.pt4.ts b/public/docs/_examples/toh-4/ts/app/app.component.pt4.ts new file mode 100644 index 0000000000..aed30ab470 --- /dev/null +++ b/public/docs/_examples/toh-4/ts/app/app.component.pt4.ts @@ -0,0 +1,111 @@ +// #docregion +import {Component} from 'angular2/angular2'; + +@Component({selector: 'my-app'}) +export class AppComponent { } +// #enddocregion + +// #docregion initialize-routes-property +export class AppComponent { + public title = 'Tour of Heroes'; + public routes = Routes; +} +// #enddocregion initialize-routes-property + +// #docregion oninit +onInit() { + this.heroes = this.getHeroes(); +} + +getHeroes() { + this.heroes = []; + + this._heroService.getHeroes() + .then((heroes: Hero[]) => this.heroes = heroes); + + return this.heroes; +} +// #enddocregion oninit + +// #docregion styles +styles: [` + .router-link {padding: 5px;text-decoration: none;} + .router-link:visited, .router-link:link {color: #444;} + .router-link:hover {color: white; background-color: #1171a3; text-decoration: none;} + .router-link.router-link-active {color: white; background-color: #52b9e9; text-decoration: none;} +`], +// #enddocregion styles + +// #docregion import-params +import {RouteParams} from 'angular2/router'; +// #enddocregion import-params +// #docregion inject-routeparams +constructor(private _routeParams: RouteParams) {} +// #enddocregion inject-routeparams +// #docregion access-params +export class HeroDetailComponent implements OnInit { + +onInit() { } +// #enddocregion access-params +// #docregion import-onit +import {Component, FORM_DIRECTIVES, OnInit} from 'angular2/angular2'; +// #enddocregion import-onit +// #docregion onit-id-param +onInit() { let id = +this._routeParams.get('id'); // TODO: get the hero using it’s id } +// #enddocregion onit-id-param + +// #docregion onit-hero-id +onInit() { + if (!this.hero) { + let id = +this._routeParams.get('id'); + // TODO: get the hero using it’s id + } +} +// #docregion onit-hero-id + +// #docregion inject-hero-service +constructor( + private _heroService: HeroService, + private _routeParams: RouteParams) { +} +// #docregion inject-hero-service + +// #docregion onit-hero-method + onInit() { + if (!this.hero) { + let id = +this._routeParams.get('id'); + this._heroService.getHero(id).then((hero: Hero) => this.hero = hero); + } + } +// #docregion onit-hero-method + +// #docregion select-hero +import {Router} from 'angular2/router'; +import {Routes} from './route.config'; + +constructor(private _heroService: HeroService, private _router: Router) { } + +gotoDetail() { + this._router.navigate([`/${Routes.detail.as}`, { id: this.selectedHero.id }]); +} +// #enddocregion select-hero + +// #docregion reference-heroes-component +@Component({ + selector: 'my-heroes', + templateUrl: 'app/heroes.component.html', + styleUrls: ['app/heroes.component.css'], + directives: [FORM_DIRECTIVES, HeroDetailComponent] +}) +export class HeroesComponent { +// #docregion reference-heroes-component + +// #docregion reference-hero-detail-component +@Component({ + selector: 'my-hero-detail', + templateUrl: 'app/hero-detail.component.html', + directives: [FORM_DIRECTIVES], + inputs: ['hero'] +}) +export class HeroDetailComponent { +// #enddocregion reference-hero-detail-component \ No newline at end of file diff --git a/public/docs/_examples/toh-4/ts/app/bootstrap.pt4.ts b/public/docs/_examples/toh-4/ts/app/bootstrap.pt4.ts new file mode 100644 index 0000000000..f7ba365121 --- /dev/null +++ b/public/docs/_examples/toh-4/ts/app/bootstrap.pt4.ts @@ -0,0 +1,13 @@ +// #docregion +import {bootstrap} from 'angular2/angular2'; +import {AppComponent} from './app.component'; +// #docregion import-hero-service +import {HeroService} from './hero.service'; +// #enddocregion import-hero-service +bootstrap(AppComponent, [HeroService]); +// #enddocregion + +// #docregion import-router +import {Router} from 'angular2/router'; +import {Routes} from './route.config'; +// #enddocregion import-router \ No newline at end of file diff --git a/public/docs/_examples/toh-4/ts/app/hero.service.pt4.ts b/public/docs/_examples/toh-4/ts/app/hero.service.pt4.ts new file mode 100644 index 0000000000..a30e6632e6 --- /dev/null +++ b/public/docs/_examples/toh-4/ts/app/hero.service.pt4.ts @@ -0,0 +1,8 @@ +// #docregion get-hero-method + getHero(id: number) { + return Promise.resolve(HEROES) + .then((heroes: Hero[]) => { return heroes.filter((h) => { + return h.id === id; + })[0]}); + } +// #docregion get-hero-method \ No newline at end of file diff --git a/public/docs/_examples/toh-4/ts/app/index.pt4.html b/public/docs/_examples/toh-4/ts/app/index.pt4.html new file mode 100644 index 0000000000..dd397ddbc8 --- /dev/null +++ b/public/docs/_examples/toh-4/ts/app/index.pt4.html @@ -0,0 +1,186 @@ + + + + + + + + + + + + +System.import('app/bootstrap'); + + +import {Component} from 'angular2/angular2'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + ` +}) +export class AppComponent { + public title = 'Tour of Heroes'; +} + + + + import {Component} from 'angular2/angular2'; + import {RouteConfig} from 'angular2/router'; + + import {APP_ROUTES} from './route.config'; + + + @Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + ` + }) + @RouteConfig(APP_ROUTES) + export class AppComponent { + public title = 'Tour of Heroes'; + } + + + + template: ` +

    {{title}}

    + +` + + + +import {Component} from 'angular2/angular2'; + +@Component({ + selector: 'my-dashboard', + template: ` +

    Dashboard Goes Here

    + ` +}) +export class DashboardComponent {} + + + +template: ` +

    {{title}}

    + Dashboard + Heroes + +`, + + + +template: ` +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    +` + + + +[class*='col-'] { float: left; } +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { text-align: center; margin-bottom: 0; } +[class*='col-'] { padding-right: 20px; padding-bottom: 20px;} +[class*='col-']:last-of-type { padding-right: 0; } +.grid { margin: 0 10em; } +.col-1-4 { width: 25%; } +.module { + padding: 20px; text-align: center; + color: #eee; max-height: 120px; min-width: 120px; + background-color: #1171a3; +} +.module:hover { background-color: #52b9e9; cursor: pointer; } +.grid-pad { padding: 20px 0 20px 20px; } +.grid-pad > [class*='col-']:last-of-type { padding-right: 20px; } +@media (max-width: 600px) { + .module { font-size: 10px; max-height: 75px; } +} +@media (max-width: 1024px) { + .grid { margin: 0; } + .module { min-width: 60px; } +} + + + +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    + + + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + Dashboard + Heroes + + `, + styles: [` + .router-link {padding: 5px;text-decoration: none;} + .router-link:visited, .router-link:link {color: #444;} + .router-link:hover {color: white; background-color: #1171a3; + text-decoration: none;} + .router-link.router-link-active {color: white; + background-color: #52b9e9; text-decoration: none;} + `], + directives: [ROUTER_DIRECTIVES] +}) + + + +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    + + + +
    +

    My Heroes

    + +
    +

    {{selectedHero.name | uppercase}} is my hero

    + +
    +
    + + + +h2 { color: #444; font-weight: lighter; } +body { margin: 2em; } +body, input[text], button { color: #888; font-family: Cambria, Georgia; } +button {padding: 0.2em; font-size: 14px} + \ No newline at end of file diff --git a/public/docs/_examples/toh-4/ts/app/route.config.pt4.ts b/public/docs/_examples/toh-4/ts/app/route.config.pt4.ts new file mode 100644 index 0000000000..8ba6169c5c --- /dev/null +++ b/public/docs/_examples/toh-4/ts/app/route.config.pt4.ts @@ -0,0 +1,50 @@ +// #docregion first-route +import {HeroesComponent} from './heroes.component'; + +export var Routes = { + heroes: { + path: '/, + as: 'Heroes', + component: HeroesComponent + } +}; + +export const APP_ROUTES = Object.keys(Routes).map(r => Routes[r]); +// #enddocregion first-route + +// #docregion dashboard-route +import {HeroesComponent} from './heroes.component'; +import {DashboardComponent} from './dashboard.component'; + +export var Routes = { + dashboard: { + path: '/', + as: 'Dashboard', + component: DashboardComponent + }, + heroes: { + path: '/heroes', + as: 'Heroes', + component: HeroesComponent + } +}; + +export const APP_ROUTES = Object.keys(Routes).map(r => Routes[r]); +// #enddocregion dashboard-route + +// #docregion route-parameter-import +import {HeroDetailComponent} from './hero-detail.component'; +// #enddocregion route-parameter-import +// #docregion route-parameter-detail +detail: { + path: '/detail/:id', + as: 'Detail', + component: HeroDetailComponent +} +// #docregion route-parameter-detail + +// #docregion router-navigate-method +gotoDetail(hero: Hero) { + this._router.navigate([`/${Routes.detail.as}`, { id: hero.id }]); +} +// #docregion router-navigate-method \ No newline at end of file diff --git a/public/docs/_examples/tsd.json b/public/docs/_examples/tsd.json deleted file mode 100644 index 0bed83f7e6..0000000000 --- a/public/docs/_examples/tsd.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "typings", - "bundle": "typings/tsd.d.ts", - "installed": { - "angular2/angular2.d.ts": { - "commit": "cd2e71bb1f0459197e733be66fdeafaec600514d" - }, - "es6-promise/es6-promise.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "jasmine/jasmine.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "rx/rx.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "rx/rx-lite.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - } - } -} diff --git a/public/docs/_examples/tutorial/ts/.gitignore b/public/docs/_examples/tutorial/ts/.gitignore new file mode 100644 index 0000000000..1855c2ea24 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/.gitignore @@ -0,0 +1,6 @@ +typings +**/*.js +**/*.map +node_modules +jspm_packages +bower_components \ No newline at end of file diff --git a/public/docs/_examples/tutorial/ts/app/app.component.ts b/public/docs/_examples/tutorial/ts/app/app.component.ts new file mode 100644 index 0000000000..d881c50343 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/app.component.ts @@ -0,0 +1,31 @@ +import {Component} from 'angular2/core'; +import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; +import {HeroesComponent} from './heroes.component'; +import {HeroDetailComponent} from './hero-detail.component'; +import {DashboardComponent} from './dashboard.component'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + Dashboard + Heroes + + `, + styles: [` + a {padding: 5px;text-decoration: none;} + a:visited, a:link {color: #444;} + a:hover {color: white; background-color: #1171a3;} + a.router-link-active {color: white; background-color: #52b9e9;} + `], + directives: [ROUTER_DIRECTIVES] +}) +@RouteConfig([ + // {path: '/', redirectTo: ['Dashboard'] }, + {path: '/dashboard', name: 'Dashboard', component: DashboardComponent, useAsDefault: true}, + {path: '/heroes', name: 'Heroes', component: HeroesComponent}, + {path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent} +]) +export class AppComponent { + public title = 'Tour of Heroes'; +} diff --git a/public/docs/_examples/tutorial/ts/app/boot.ts b/public/docs/_examples/tutorial/ts/app/boot.ts new file mode 100644 index 0000000000..c8f833639e --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/boot.ts @@ -0,0 +1,9 @@ +import {bootstrap} from 'angular2/platform/browser'; +import {ROUTER_PROVIDERS} from 'angular2/router'; +import {HeroService} from './hero.service'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent, [ + ROUTER_PROVIDERS, + HeroService +]); diff --git a/public/docs/_examples/tutorial/ts/app/dashboard.component.css b/public/docs/_examples/tutorial/ts/app/dashboard.component.css new file mode 100644 index 0000000000..26a8f3008d --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/dashboard.component.css @@ -0,0 +1,37 @@ +[class*='col-'] { float: left; } + +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +h3 { text-align: center; margin-bottom: 0; } + +[class*='col-'] { padding-right: 20px; padding-bottom: 20px;} +[class*='col-']:last-of-type { padding-right: 0; } + +.grid { margin: 0 10em; } +.col-1-4 { width: 25%; } +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #1171a3; +} + +.module:hover { background-color: #52b9e9; cursor: pointer; } + +.grid-pad { padding: 20px 0 20px 20px; } +.grid-pad > [class*='col-']:last-of-type { padding-right: 20px; } + +@media (max-width: 600px) { + .module { font-size: 10px; max-height: 75px; } +} + +@media (max-width: 1024px) { + .grid { margin: 0; } + .module { min-width: 60px; } +} diff --git a/public/docs/_examples/tutorial/ts/app/dashboard.component.html b/public/docs/_examples/tutorial/ts/app/dashboard.component.html new file mode 100644 index 0000000000..226b5c5445 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/dashboard.component.html @@ -0,0 +1,8 @@ +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    diff --git a/public/docs/_examples/tutorial/ts/app/dashboard.component.ts b/public/docs/_examples/tutorial/ts/app/dashboard.component.ts new file mode 100644 index 0000000000..2710a655cd --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/dashboard.component.ts @@ -0,0 +1,23 @@ +import {Component, OnInit} from 'angular2/core'; +import {Router} from 'angular2/router'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; + +@Component({ + selector: 'my-dashboard', + templateUrl: 'app/dashboard.component.html', + styleUrls: ['app/dashboard.component.css'] +}) +export class DashboardComponent implements OnInit { + public heroes: Hero[] = []; + + constructor(private _heroService: HeroService, private _router: Router) { } + + ngOnInit() { + this._heroService.getHeroes().then(heroes => this.heroes = heroes); + } + + gotoDetail(hero: Hero) { + this._router.navigate(['HeroDetail', { id: hero.id }]); + } +} diff --git a/public/docs/_examples/tutorial/ts/app/hero-detail.component.html b/public/docs/_examples/tutorial/ts/app/hero-detail.component.html new file mode 100644 index 0000000000..af9039dc9f --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/hero-detail.component.html @@ -0,0 +1,10 @@ +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + +
    \ No newline at end of file diff --git a/public/docs/_examples/tutorial/ts/app/hero-detail.component.ts b/public/docs/_examples/tutorial/ts/app/hero-detail.component.ts new file mode 100644 index 0000000000..3ac2ecca31 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/hero-detail.component.ts @@ -0,0 +1,28 @@ +import {Component, OnInit} from 'angular2/core'; +import {RouteParams} from 'angular2/router'; +import {Hero} from './hero'; +import {HeroService} from './hero.service'; + +@Component({ + selector: 'my-hero-detail', + templateUrl: 'app/hero-detail.component.html', + inputs: ['hero'] +}) +export class HeroDetailComponent implements OnInit { + public hero: Hero; + + constructor(private _heroService: HeroService, + private _routeParams: RouteParams) { + } + + ngOnInit() { + if (!this.hero) { + let id = +this._routeParams.get('id'); + this._heroService.getHero(id).then(hero => this.hero = hero); + } + } + + goBack() { + window.history.back(); + } +} diff --git a/public/docs/_examples/tutorial/ts/app/hero.service.ts b/public/docs/_examples/tutorial/ts/app/hero.service.ts new file mode 100644 index 0000000000..45b4e25adf --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/hero.service.ts @@ -0,0 +1,14 @@ +import {Injectable} from 'angular2/core'; +import {HEROES} from './mock-heroes'; + +@Injectable() +export class HeroService { + getHeroes() { + return Promise.resolve(HEROES); + } + + getHero(id: number) { + return Promise.resolve(HEROES) + .then(heroes => heroes.filter(h => h.id === id)[0]); + } +} diff --git a/public/docs/_examples/tutorial/ts/app/hero.ts b/public/docs/_examples/tutorial/ts/app/hero.ts new file mode 100644 index 0000000000..34f6058c2f --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/hero.ts @@ -0,0 +1,4 @@ +export interface Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/tutorial/ts/app/heroes.component.css b/public/docs/_examples/tutorial/ts/app/heroes.component.css new file mode 100644 index 0000000000..febf0e1e21 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/heroes.component.css @@ -0,0 +1,17 @@ +.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} + +.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } + +.heroes li:hover {color: #369; background-color: #EEE; left: .2em;} + +.heroes .badge { + font-size: small; + color: white; + padding: 0.1em 0.7em; + background-color: #369; + line-height: 1em; + position: relative; + left: -1px; + top: -1px; +} +.selected { background-color: #EEE; color: #369; } \ No newline at end of file diff --git a/public/docs/_examples/tutorial/ts/app/heroes.component.html b/public/docs/_examples/tutorial/ts/app/heroes.component.html new file mode 100644 index 0000000000..c54fdc17aa --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/heroes.component.html @@ -0,0 +1,14 @@ +
    +

    My Heroes

    + +
    +

    {{selectedHero.name | uppercase}} is my hero

    + +
    +
    diff --git a/public/docs/_examples/tutorial/ts/app/heroes.component.ts b/public/docs/_examples/tutorial/ts/app/heroes.component.ts new file mode 100644 index 0000000000..22a3e82a4a --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/heroes.component.ts @@ -0,0 +1,37 @@ +import {Component, OnInit} from 'angular2/core'; +import {Router} from 'angular2/router'; +import {HeroService} from './hero.service'; +import {HeroDetailComponent} from './hero-detail.component'; +import {Hero} from './hero'; + +@Component({ + selector: 'my-heroes', + templateUrl: 'app/heroes.component.html', + styleUrls: ['app/heroes.component.css'], + directives: [HeroDetailComponent] +}) +export class HeroesComponent implements OnInit { + public heroes: Hero[]; + public selectedHero: Hero; + + constructor(private _heroService: HeroService, private _router: Router) { } + + getHeroes() { + this.selectedHero = undefined; + this.heroes = []; + + this._heroService.getHeroes().then(heroes => this.heroes = heroes); + + return this.heroes; + } + + gotoDetail() { + this._router.navigate(['HeroDetail', { id: this.selectedHero.id }]); + } + + ngOnInit() { + this.heroes = this.getHeroes(); + } + + onSelect(hero: Hero) { this.selectedHero = hero; } +} diff --git a/public/docs/_examples/tutorial/ts/app/mock-heroes.ts b/public/docs/_examples/tutorial/ts/app/mock-heroes.ts new file mode 100644 index 0000000000..406e5eb7f8 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/app/mock-heroes.ts @@ -0,0 +1,14 @@ +import { Hero } from './hero'; + +export var HEROES: Hero[] = [ + {"id": 11, "name": "Mr. Nice"}, + {"id": 12, "name": "Narco"}, + {"id": 13, "name": "Bombasto"}, + {"id": 14, "name": "Celeritas"}, + {"id": 15, "name": "Magneta"}, + {"id": 16, "name": "RubberMan"}, + {"id": 17, "name": "Dynama"}, + {"id": 18, "name": "Dr IQ"}, + {"id": 19, "name": "Magma"}, + {"id": 20, "name": "Tornado"} +]; diff --git a/public/docs/_examples/tutorial/ts/example-config.json b/public/docs/_examples/tutorial/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/tutorial/ts/index.html b/public/docs/_examples/tutorial/ts/index.html new file mode 100644 index 0000000000..2f7e00b2f3 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + Loading... + + + \ No newline at end of file diff --git a/public/docs/_examples/tutorial/ts/plnkr.json b/public/docs/_examples/tutorial/ts/plnkr.json new file mode 100644 index 0000000000..fc4ea3e22c --- /dev/null +++ b/public/docs/_examples/tutorial/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Tour of Heroes", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/public/docs/_examples/tutorial/ts/styles.css b/public/docs/_examples/tutorial/ts/styles.css new file mode 100644 index 0000000000..8fa646e1c7 --- /dev/null +++ b/public/docs/_examples/tutorial/ts/styles.css @@ -0,0 +1,4 @@ +h2 { color: #444; font-weight: lighter; } +body { margin: 2em; } +body, input[text], button { color: #888; font-family: Cambria, Georgia; } +button {padding: 0.2em; font-size: 14px} diff --git a/public/docs/ts/latest/quickstart.jade b/public/docs/ts/latest/quickstart.jade index 8a5deab6da..e3fc4ba048 100644 --- a/public/docs/ts/latest/quickstart.jade +++ b/public/docs/ts/latest/quickstart.jade @@ -522,10 +522,9 @@ code-example(format=""). We might wish to render the first page of our application on the server to improve launch performance or facilitate [SEO](http://static.googleusercontent.com/media/www.google.com/en//webmasters/docs/search-engine-optimization-starter-guide.pdf). - - These targes require a different kind of bootstrap function that we'd import from a different library. - + These targets require a different kind of bootstrap function that we'd import from a different library. + #### Why do we create a separate ***boot.ts*** file? The *boot.ts* file is tiny. This is just a QuickStart. diff --git a/public/docs/ts/latest/tutorial/dependency-injection.1.jade b/public/docs/ts/latest/tutorial/dependency-injection.1.jade new file mode 100644 index 0000000000..c8fab3f81f --- /dev/null +++ b/public/docs/ts/latest/tutorial/dependency-injection.1.jade @@ -0,0 +1,623 @@ +include ../../../../_includes/_util-fns +:markdown + Dependency Injection is an important application design pattern. + Angular has its own Dependency Injection framework and + we really can't build an Angular application without it. + + In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it. + +.l-main-section +:markdown + ## Why Dependency Injection? + + Let's start with the following code. + + ``` + class Engine {} + + class Tires {} + + class Car { + private engine: Engine; + private tires: Tires; + + constructor() { + this.engine = new Engine(); + this.tires = new Tires(); + } + // Method using the engine and tires + drive() {} + } + ``` + + Our `Car` creates everything it needs inside its constructor. + What's the problem? + + The problem is that our `Car` class is brittle, inflexible, and hard to test. + + Our `Car` needs an engine and tires. Instead of asking for them, + the `Car` constructor creates its own copies by "new-ing" them from + the very specific classes, `Engine` and `Tires`. + + What if the `Engine` class evolves and its constructor requires a parameter? + Our `Car` is broken and stays broken until we rewrite it along the lines of + `this.engine = new Engine(theNewParameter)`. + We didn't care about `Engine` constructor parameters when we first wrote `Car`. + We don't really care about them now. + But we'll *have* to start caring because + when the definion of `Engine` changes, our `Car` class must change. + That makes `Car` brittle. + + What if we want to put a different brand of tires on our `Car`. Too bad. + We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible. + + Right now each new car gets its own engine. It can't share an engine with other cars. + While that makes sense for an automobile engine, + we can think of other dependencies that should be shared ... like the onboard + wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility + to share services that have been created previously for other consumers. + + When we write tests for our `Car` we're at the mercy of its hidden dependencies. + Is it even possible to create a new `Engine` in a test environment? + What does `Engine`itself depend upon? What does that dependency depend on? + Will a new instance of `Engine` make an asynchronous call to the server? + We certainly don't want that going on during our tests. + + What if our `Car` should flash a warning signal when tire pressure is low. + How do we confirm that if actually does flash a warning + if we can't swap in low-pressure tires during the test? + + We have no control over the car's hidden dependencies. + When we can't control the dependencies, a class become difficult to test. + + How can we make `Car` more robust, more flexible, and more testable? + + That's super easy. We probably already know what to do. We change our `Car` constructor to this: + + ``` + constructor(engine: Engine, tires: Tires) { + this.engine = engine; + this.tires = tires; + } + ``` + See what happened? We moved the definition of the dependencies to the constructor. + Our `Car` class no longer creates an engine or tires. + It just consumes them. + + Now we create a car by passing the engine and tires to the constructor. + ``` + var car = new Car(new Engine(), new Tires()); + ``` + How cool is that? + The definition of the engine and tire dependencies are decoupled from the `Car` class itself. + We can pass in any kind of engine or tires we like, as long as they + conform to the general API requirements of an engine or tires. + + If someone extends the `Engine` class, that is not `Car`'s problem. +.l-sub-section + :markdown + The consumer of `Car` has the problem. The consumer must update the car creation code to + something like: + ``` + var car = new Car(new Engine(theNewParameter), new Tires()); + ``` + The critical point is this: `Car` itself did not have to change. + We'll take care of the consumer's problem soon enough. + +:markdown + The `Car` class is much easier to test because we are in complete control + of its dependencies. + We can pass mocks to the constructor that do exactly what we want them to do + during each test: + ``` + var car = new Car(new MockEngine(), new MockLowPressureTires()); + ``` + + **We just learned what Dependency Injection is**. + + It's a coding pattern in which a class receives its dependencies from external + sources rather than creating them itself. + + Cool! But what about that poor consumer? + Anyone who wants a `Car` must now + create all three parts: the `Car`, `Engine`, and `Tires`. + The `Car` class shed its problems at the consumer's expense. + We need something that takes care of assembling these parts for us. + + We could write a giant class to do that: + ``` + class SuperFactory { + createEngine = () => new Engine(); + createTires = () => new Tires(); + createCar = () => new Car(this.createEngine(), this.createTires()); + } + ``` + It's not so bad now with only three creation methods. + But maintaining it will be hairy as the application grows. + This `SuperFactory` is going to become a huge spider web of + interdependent factory methods! + + Wouldn't it be nice if we could simply list the things we want to build without + having to define which dependency gets injected into what? + + This is where the Dependency Injection Framework comes into play. + Imagine the framework had something called an `Injector`. + We register some classes with this `Injector` and it figures out how to create them. + + When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go. + ``` + function main() { + var injector = new Injector([Car, Engine, Tires, Logger]); + var car = injector.get(Car); + car.drive(); + } + ``` + Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. + The consumer knows nothing about creating a `Car`. + We don't have a gigantic factory class to maintain. + Both `Car` and consumer simply ask for what they need and the `Injector` delivers. + + This is what a **Dependency InjectionFramework** is all about. + + Now that we know what Dependency Injection is and appreciate its benefits, + let's see how it is implemented in Angular. + +.l-main-section +:markdown + ## Angular Dependency Injection + + Angular ships with its own Dependency Injection framework. This framework can also be used + as a standalone module by other applications and frameworks. + + That sounds nice. What does it do for us when building components in Angular? + Let's see, one step at a time. + + We'll begin with a simplified version of the `HeroesComponent` + that we built in the [The Tour of Heroes](../tutorial/). + ``` + import {Component} from 'angular2/angular2'; + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + @Component({ + selector: 'my-heroes' + templateUrl: 'app/heroes.component.html' + }) + export class HeroesComponent { + + heroes: Hero[] = HEROES; + + } + ``` + It assigns a list of mocked heroes to its `heroes` property for binding within the template. + Pretty straight forward. + + Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component. + That works in the early stages of development but it's far from ideal. + As soon as we try to test this component or want to get our heroes data from a remote server, + we'll have to change this component's implementation of `heroes` and + fix every other use of the `HEROES` mock data. + + Let's make a service that hides how we get Hero data. +.l-sub-section + :markdown + Write this service in its own file. See [this note](#forward-ref) to understand why. +:markdown + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + class HeroService { + + heroes: Hero[]; + + constructor() { + this.heroes = HEROES; + } + + getHeroes() { + return this.heroes; + } + } + ``` + Our `HeroService` exposes a `getHeroes()` method that returns + the same mock data as before but none of its consumers need to know that. + + A service is nothing more than a class in Angular 2. + It remains nothing more than a class until we register it with + the Angular injector. + + ### Configuring the Injector + + We don't have to create the injector. + + Angular creates an application-wide injector for us during the bootstrap process. + ``` + bootstrap(HeroesComponent); + ``` + + Let’s configure the injector at the same time that we bootstrap by adding + our `HeroService` to an array in the second argument. + We'll explain that array when we talk about [providers](#providers) later in this chapter. + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application. + + ### Preparing the `HeroesComponent` for injection + + The `HeroesComponent` should get its heroes from this service. + Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained + earlier](#ctor-injection)". + + ``` + constructor(heroService: HeroService) { + this.heroes = heroService.getHeroes(); + } + ``` + +.l-sub-section + :markdown + Adding a parameter to the constructor isn't all that's happening here. + + We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`. + The class is also decorated with the `@Component` decorator (scroll up to confirm that fact). + + When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata + into the generated JavaScript code. Within that metadata lurks the information that + associates the `heroService` parameter with the `HeroService` class. + + That's how the Angular injector will know to inject an instance of the `HeroService` when it + creates a new `HeroesComponent`. +:markdown + ### Creating the `HeroesComponent` with the injector (implicitly) + When we introduced the idea of an injector above, we showed how to create + a new `Car` with that injector. + ``` + var car = injector.get(Car); + ``` + Search the entire Tour of Heroes source. We won't find a single line like + ``` + var hc = injector.get(HeroesComponent); + ``` + We *could* write code like that if we wanted to. We just don't have to. + Angular does that for us when it renders a `HeroesComponent` + whether we ask for it in an HTML template ... + ``` + + ``` + ... or navigate to a `HeroesComponent` view with the [router](./router.html). + + ### Singleton services + We might wonder what happens when we inject the `HeroService` into other components. + Do we get the same instance every time? + + Yes we do. Dependencies are singletons. + We’ll discuss that later in our chapter about + [Hierarchical Injectors](./hierarchical-dependency-injection.html). + + ### Testing the component + We emphasized earlier that designing a class for dependency injection makes it easier to test. + + Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`. + We simply create a bew `HeroesComponent` with a mock service and poke at it: + ``` + it("should have heroes when created", () => { + let hc = new HeroesComponent(mockService); + expect(hc.heroes.length).toEqual(mockService.getHeroes().length); + }) + ``` + ### When the service needs a service + Our `HeroService` is very simple. It doesn't have any dependencies of its own. + + + What if it had a dependency? What if it reported its activities through a logging service? + We'd apply the same "constructor injection" pattern. + + Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter. + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + import {Logger} from './logger'; + + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger) { + this.heroes = HEROES; + } + + getHeroes() { + this.logger.log('Getting heroes ...') + return this.heroes; + } + } + ``` + The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`. + We call that property within our `getHeroes()` method when anyone asks for heroes. + + **The `@Injectable()` decoration catches our eye!** + +.alert.is-critical + :markdown + **Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses. + Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.** +:markdown + We haven't seen `@Injectable()` before. + As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then. + + We need it now ... now that our service has an injected dependency. + We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. + As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. . + + The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`? + We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`. + TypeScript generates metadata for *any* class with a decorator and *any* decorator will do. + +.l-main-section +:markdown + + ## Injector Providers + + Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process? + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That list of classes is actually a list of **providers**. + + "Providers" create the instances of the things that we ask the injector to inject. + There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`. + A class is a natural provider - it's meant to be created. But it's not the only way + to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call. + Any of these approaches might be a good choice under the right circumstances. + + What matters is that the injector knows what to do when something asks for a `HeroService`. + + ### Provider mappings + When we registered the `HeroService` with the injector, we were actually registering + a mapping between the `HeroService` *token* and a provider that can create a `HeroService`. + + When we wrote ... + ``` + import {bootstrap} from 'angular2/angular2'; + + bootstrap(AppComponent, [HeroService]); + ``` + ... Angular translated that statement into a mapping instruction involving the Angular `provide` method + ``` + import {bootstrap, provide} from 'angular2/angular2'; + + bootstrap(AppComponent, [ + provide(HeroService, {useClass:HeroService}) + ]); + ``` + Of course we prefer the shorthand syntax - `[HeroService]` - when the provider and the token are the same class. + + Isn't that always the case? Not always. + + ### Alternative Class Providers + + Occasionally we'll ask a different class to provide the service. + + We do that regularly when testing a component that we're creating with dependency injection. + In this example, we tell the injector + to return a `MockHeroService` when something asks for the `HeroService`. + ``` + beforeEachProviders(() => [ + provide(HeroService, {useClass: MockHeroService}); + ]); + ``` + ### Value Providers + + Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. + + We do that a lot when we write tests. We might write the following test setup + for tests that explore how the `HeroComponent` behaves when the `HeroService` + returns an empty hero list. + ``` + beforeEachProviders(() => { + + let emptyHeroService = { getHeroes: () => [] }; + + return [ provide(HeroService, {useValue: emptyHeroService}) ]; + }); + ``` + Notice that we mapped with `useValue` instead of `useClass`. + + ### Factory Providers + + Sometimes the best choice for a provider is neither a class nor a value. + + Suppose our HeroService has some cool new feature that we're only offering to "special" users. + The HeroService shouldn't know about users and + we won't know if the current user is special until runtime anyway. + We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag + that toggles the feature on or off. + We rewrite the `HeroService` again as follows. + ``` + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger, private useCoolFeature: boolean) { + this.heroes = HEROES; + } + + getHeroes() { + let msg = this.useCoolFeature ? 'the cool new way' : 'the old way'; + this.logger.log('Getting heroes ...' + msg) + return this.heroes; + } + } + ``` + The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a + simple flag. + + We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user. + We'll' build up to that result, beginning with our definition of the factory function: + ``` + let heroServiceFactory = (logger: Logger, userService: UserService) => { + return new HeroService(logger, userService.user.isSpecial); + } + ``` +.l-sub-section + :markdown + The factory takes two parameters: the logger service and a user service. + The logger we pass straight to the constructor as we did before. + + We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true, + a fact we can't know until runtime. +:markdown + We use dependency injection everywhere so of course the factory function depends on + two injected services: `Logger` and `UserService`. + We declare those requirements in our provider definition object: + ``` + let heroServiceDefinition = { + useFactory: heroServiceFactory, + deps: [Logger, UserService] + }; + ``` +.l-sub-section + :markdown + The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`. + + The `deps` property is an array of provider mapping tokens. + The `Logger` and `UserService` classes serve as tokens for their own class provider mappings. +:markdown + Finally, we create the mapping and adjust the bootstrapping to include that mapping in its provider configuration. + ``` + let heroServiceMapping = provide(HeroService, heroServiceDefinition); + + bootstrap(AppComponent, [heroServiceMapping, Logger, UserService]); + ``` + ### String tokens + + Sometimes we have an object dependency rather than a class dependency. + + Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint. + These configuration objects aren't always instances of a class. They're just objects ... like this one: + ``` + let config = { + apiEndpoint: 'api.heroes.com', + title: 'The Hero Employment Agency' + }; + ``` + We'd like to make this `config` object available for injection. + We know we can register an object with a "Value Provider". But what do we use for the token? + + Until now, we've always had a class to use as the token for mapping. + The `HeroService` class was our token, whether we mapped it to another class, a value, or a factory provider. + This time we don't have a class. There is no `Config` class. + + Fortunately, a token can be either a JavaScript type (e.g. the class function) **or a string**. We'll map our configuration object + to a string! + ``` + bootstrap(AppComponent, [ + // other mappings // + provide('App.config', {useValue:config}) + ]); + ``` + Now let's update the `HeroesComponent` constructor so it can display the configured title. + Right now the constructor signature is + ``` + constructor(heroService: HeroService) + ``` + We might think we can write: + ``` + // FAIL! + constructor(heroService: HeroService, config: config) + ``` + That's not going to work. There is no type called `config` and we didn't register the `config` object under that name anyway. + We'll need a little help from another Angular decorator called `@Inject`. + ``` + import {Inject} from 'angular2/angulare2' + + constructor(heroService: HeroService, @Inject('app.config') config) + + ``` + +.l-main-section +:markdown + # Next Steps + We learned the basics of Angular Dependency Injection in this chapter. + + The Angular Dependency Injection is more capable than we've described. + We can learn more about its advanced features, beginning with its support for + a hierarchy of nested injectors in the next + [Dependency Injection chapter](./hierarchical-dependency-injection.html) + +.l-main-section + +:markdown + ### Appendix: Why we recommend one class per file + Developers expect one class per file. Multiple classes per file is confusing and is best avoided. + If we define every class in its own file, there is nothing in this note to worry about. + Move along! + + If we scorn this advice + and we add our `HeroService` class to the `HeroesComponent` file anyway, + **define the `HeroesComponent` last!** + If we put it define component before the service, + we'll get a runtime null reference error. + + To understand why, paste the following incorrect, ultra-simplified rendition of these two + classes into the [TypeScript playground](http://www.typescriptlang.org/Playground). + + ``` + class HeroesComponent { + static $providers=[HeroService] + } + + class HeroService { } + + alert(HeroesComponent.$providers) + ``` +.l-sub-section + :markdown + The `HeroService` is incorrectly defined below the `HeroComponent`. + + The `$providers` static property represents the metadata about the injected `HeroService` + that TypeScript compiler would add to the component class. + + The `alert` simulates the action of the Dependency Injector at runtime + when it attempts to create a `HeroesComponent`. +:markdown + Run it. The alert appears but displays nothing. + This is the equivalent of the null reference error thrown at runtime. + + We understand why when we review the generated JavaScript which looks like this: + ``` + var HeroesComponent = (function () { + function HeroesComponent() { + } + HeroesComponent.$providers = [HeroService]; + return HeroesComponent; + })(); + + var HeroService = (function () { + function HeroService() { + } + return HeroService; + })(); + + alert(HeroesComponent.$providers); + ``` + + Notice that the TypeScript compiler turns classes into function expressions + assigned to variables. The value of the captured `HeroService` variable is undefined + when the `$providers` array is assigned. The `HeroService` variable gets its value too late + to be captured. + + Reverse the order of class definition so that the `HeroService` + appears before the `HeroesComponent` that requires it. + Run again. This time the alert displays the `HeroService` function definition. + + If we insist on defining the `HeroService` in the same file and insist on + defining the component first, Angular offers a way to make that work. + The `forwardRef()` method let's us reference a class + before it has been defined. + Learn more about this problem and the `forwardRef()` + in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). diff --git a/public/docs/ts/latest/tutorial/dependency-injection.jade b/public/docs/ts/latest/tutorial/dependency-injection.jade new file mode 100644 index 0000000000..c8fab3f81f --- /dev/null +++ b/public/docs/ts/latest/tutorial/dependency-injection.jade @@ -0,0 +1,623 @@ +include ../../../../_includes/_util-fns +:markdown + Dependency Injection is an important application design pattern. + Angular has its own Dependency Injection framework and + we really can't build an Angular application without it. + + In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it. + +.l-main-section +:markdown + ## Why Dependency Injection? + + Let's start with the following code. + + ``` + class Engine {} + + class Tires {} + + class Car { + private engine: Engine; + private tires: Tires; + + constructor() { + this.engine = new Engine(); + this.tires = new Tires(); + } + // Method using the engine and tires + drive() {} + } + ``` + + Our `Car` creates everything it needs inside its constructor. + What's the problem? + + The problem is that our `Car` class is brittle, inflexible, and hard to test. + + Our `Car` needs an engine and tires. Instead of asking for them, + the `Car` constructor creates its own copies by "new-ing" them from + the very specific classes, `Engine` and `Tires`. + + What if the `Engine` class evolves and its constructor requires a parameter? + Our `Car` is broken and stays broken until we rewrite it along the lines of + `this.engine = new Engine(theNewParameter)`. + We didn't care about `Engine` constructor parameters when we first wrote `Car`. + We don't really care about them now. + But we'll *have* to start caring because + when the definion of `Engine` changes, our `Car` class must change. + That makes `Car` brittle. + + What if we want to put a different brand of tires on our `Car`. Too bad. + We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible. + + Right now each new car gets its own engine. It can't share an engine with other cars. + While that makes sense for an automobile engine, + we can think of other dependencies that should be shared ... like the onboard + wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility + to share services that have been created previously for other consumers. + + When we write tests for our `Car` we're at the mercy of its hidden dependencies. + Is it even possible to create a new `Engine` in a test environment? + What does `Engine`itself depend upon? What does that dependency depend on? + Will a new instance of `Engine` make an asynchronous call to the server? + We certainly don't want that going on during our tests. + + What if our `Car` should flash a warning signal when tire pressure is low. + How do we confirm that if actually does flash a warning + if we can't swap in low-pressure tires during the test? + + We have no control over the car's hidden dependencies. + When we can't control the dependencies, a class become difficult to test. + + How can we make `Car` more robust, more flexible, and more testable? + + That's super easy. We probably already know what to do. We change our `Car` constructor to this: + + ``` + constructor(engine: Engine, tires: Tires) { + this.engine = engine; + this.tires = tires; + } + ``` + See what happened? We moved the definition of the dependencies to the constructor. + Our `Car` class no longer creates an engine or tires. + It just consumes them. + + Now we create a car by passing the engine and tires to the constructor. + ``` + var car = new Car(new Engine(), new Tires()); + ``` + How cool is that? + The definition of the engine and tire dependencies are decoupled from the `Car` class itself. + We can pass in any kind of engine or tires we like, as long as they + conform to the general API requirements of an engine or tires. + + If someone extends the `Engine` class, that is not `Car`'s problem. +.l-sub-section + :markdown + The consumer of `Car` has the problem. The consumer must update the car creation code to + something like: + ``` + var car = new Car(new Engine(theNewParameter), new Tires()); + ``` + The critical point is this: `Car` itself did not have to change. + We'll take care of the consumer's problem soon enough. + +:markdown + The `Car` class is much easier to test because we are in complete control + of its dependencies. + We can pass mocks to the constructor that do exactly what we want them to do + during each test: + ``` + var car = new Car(new MockEngine(), new MockLowPressureTires()); + ``` + + **We just learned what Dependency Injection is**. + + It's a coding pattern in which a class receives its dependencies from external + sources rather than creating them itself. + + Cool! But what about that poor consumer? + Anyone who wants a `Car` must now + create all three parts: the `Car`, `Engine`, and `Tires`. + The `Car` class shed its problems at the consumer's expense. + We need something that takes care of assembling these parts for us. + + We could write a giant class to do that: + ``` + class SuperFactory { + createEngine = () => new Engine(); + createTires = () => new Tires(); + createCar = () => new Car(this.createEngine(), this.createTires()); + } + ``` + It's not so bad now with only three creation methods. + But maintaining it will be hairy as the application grows. + This `SuperFactory` is going to become a huge spider web of + interdependent factory methods! + + Wouldn't it be nice if we could simply list the things we want to build without + having to define which dependency gets injected into what? + + This is where the Dependency Injection Framework comes into play. + Imagine the framework had something called an `Injector`. + We register some classes with this `Injector` and it figures out how to create them. + + When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go. + ``` + function main() { + var injector = new Injector([Car, Engine, Tires, Logger]); + var car = injector.get(Car); + car.drive(); + } + ``` + Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`. + The consumer knows nothing about creating a `Car`. + We don't have a gigantic factory class to maintain. + Both `Car` and consumer simply ask for what they need and the `Injector` delivers. + + This is what a **Dependency InjectionFramework** is all about. + + Now that we know what Dependency Injection is and appreciate its benefits, + let's see how it is implemented in Angular. + +.l-main-section +:markdown + ## Angular Dependency Injection + + Angular ships with its own Dependency Injection framework. This framework can also be used + as a standalone module by other applications and frameworks. + + That sounds nice. What does it do for us when building components in Angular? + Let's see, one step at a time. + + We'll begin with a simplified version of the `HeroesComponent` + that we built in the [The Tour of Heroes](../tutorial/). + ``` + import {Component} from 'angular2/angular2'; + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + @Component({ + selector: 'my-heroes' + templateUrl: 'app/heroes.component.html' + }) + export class HeroesComponent { + + heroes: Hero[] = HEROES; + + } + ``` + It assigns a list of mocked heroes to its `heroes` property for binding within the template. + Pretty straight forward. + + Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component. + That works in the early stages of development but it's far from ideal. + As soon as we try to test this component or want to get our heroes data from a remote server, + we'll have to change this component's implementation of `heroes` and + fix every other use of the `HEROES` mock data. + + Let's make a service that hides how we get Hero data. +.l-sub-section + :markdown + Write this service in its own file. See [this note](#forward-ref) to understand why. +:markdown + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + + class HeroService { + + heroes: Hero[]; + + constructor() { + this.heroes = HEROES; + } + + getHeroes() { + return this.heroes; + } + } + ``` + Our `HeroService` exposes a `getHeroes()` method that returns + the same mock data as before but none of its consumers need to know that. + + A service is nothing more than a class in Angular 2. + It remains nothing more than a class until we register it with + the Angular injector. + + ### Configuring the Injector + + We don't have to create the injector. + + Angular creates an application-wide injector for us during the bootstrap process. + ``` + bootstrap(HeroesComponent); + ``` + + Let’s configure the injector at the same time that we bootstrap by adding + our `HeroService` to an array in the second argument. + We'll explain that array when we talk about [providers](#providers) later in this chapter. + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application. + + ### Preparing the `HeroesComponent` for injection + + The `HeroesComponent` should get its heroes from this service. + Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained + earlier](#ctor-injection)". + + ``` + constructor(heroService: HeroService) { + this.heroes = heroService.getHeroes(); + } + ``` + +.l-sub-section + :markdown + Adding a parameter to the constructor isn't all that's happening here. + + We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`. + The class is also decorated with the `@Component` decorator (scroll up to confirm that fact). + + When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata + into the generated JavaScript code. Within that metadata lurks the information that + associates the `heroService` parameter with the `HeroService` class. + + That's how the Angular injector will know to inject an instance of the `HeroService` when it + creates a new `HeroesComponent`. +:markdown + ### Creating the `HeroesComponent` with the injector (implicitly) + When we introduced the idea of an injector above, we showed how to create + a new `Car` with that injector. + ``` + var car = injector.get(Car); + ``` + Search the entire Tour of Heroes source. We won't find a single line like + ``` + var hc = injector.get(HeroesComponent); + ``` + We *could* write code like that if we wanted to. We just don't have to. + Angular does that for us when it renders a `HeroesComponent` + whether we ask for it in an HTML template ... + ``` + + ``` + ... or navigate to a `HeroesComponent` view with the [router](./router.html). + + ### Singleton services + We might wonder what happens when we inject the `HeroService` into other components. + Do we get the same instance every time? + + Yes we do. Dependencies are singletons. + We’ll discuss that later in our chapter about + [Hierarchical Injectors](./hierarchical-dependency-injection.html). + + ### Testing the component + We emphasized earlier that designing a class for dependency injection makes it easier to test. + + Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`. + We simply create a bew `HeroesComponent` with a mock service and poke at it: + ``` + it("should have heroes when created", () => { + let hc = new HeroesComponent(mockService); + expect(hc.heroes.length).toEqual(mockService.getHeroes().length); + }) + ``` + ### When the service needs a service + Our `HeroService` is very simple. It doesn't have any dependencies of its own. + + + What if it had a dependency? What if it reported its activities through a logging service? + We'd apply the same "constructor injection" pattern. + + Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter. + ``` + import {Hero} from './hero'; + import {HEROES} from './mock-heroes'; + import {Logger} from './logger'; + + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger) { + this.heroes = HEROES; + } + + getHeroes() { + this.logger.log('Getting heroes ...') + return this.heroes; + } + } + ``` + The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`. + We call that property within our `getHeroes()` method when anyone asks for heroes. + + **The `@Injectable()` decoration catches our eye!** + +.alert.is-critical + :markdown + **Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses. + Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.** +:markdown + We haven't seen `@Injectable()` before. + As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then. + + We need it now ... now that our service has an injected dependency. + We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. + As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. . + + The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`? + We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`. + TypeScript generates metadata for *any* class with a decorator and *any* decorator will do. + +.l-main-section +:markdown + + ## Injector Providers + + Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process? + ``` + bootstrap(AppComponent, [HeroService]); + ``` + That list of classes is actually a list of **providers**. + + "Providers" create the instances of the things that we ask the injector to inject. + There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`. + A class is a natural provider - it's meant to be created. But it's not the only way + to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call. + Any of these approaches might be a good choice under the right circumstances. + + What matters is that the injector knows what to do when something asks for a `HeroService`. + + ### Provider mappings + When we registered the `HeroService` with the injector, we were actually registering + a mapping between the `HeroService` *token* and a provider that can create a `HeroService`. + + When we wrote ... + ``` + import {bootstrap} from 'angular2/angular2'; + + bootstrap(AppComponent, [HeroService]); + ``` + ... Angular translated that statement into a mapping instruction involving the Angular `provide` method + ``` + import {bootstrap, provide} from 'angular2/angular2'; + + bootstrap(AppComponent, [ + provide(HeroService, {useClass:HeroService}) + ]); + ``` + Of course we prefer the shorthand syntax - `[HeroService]` - when the provider and the token are the same class. + + Isn't that always the case? Not always. + + ### Alternative Class Providers + + Occasionally we'll ask a different class to provide the service. + + We do that regularly when testing a component that we're creating with dependency injection. + In this example, we tell the injector + to return a `MockHeroService` when something asks for the `HeroService`. + ``` + beforeEachProviders(() => [ + provide(HeroService, {useClass: MockHeroService}); + ]); + ``` + ### Value Providers + + Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class. + + We do that a lot when we write tests. We might write the following test setup + for tests that explore how the `HeroComponent` behaves when the `HeroService` + returns an empty hero list. + ``` + beforeEachProviders(() => { + + let emptyHeroService = { getHeroes: () => [] }; + + return [ provide(HeroService, {useValue: emptyHeroService}) ]; + }); + ``` + Notice that we mapped with `useValue` instead of `useClass`. + + ### Factory Providers + + Sometimes the best choice for a provider is neither a class nor a value. + + Suppose our HeroService has some cool new feature that we're only offering to "special" users. + The HeroService shouldn't know about users and + we won't know if the current user is special until runtime anyway. + We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag + that toggles the feature on or off. + We rewrite the `HeroService` again as follows. + ``` + @Injectable() + class HeroService { + + heroes: Hero[]; + + constructor(private logger: Logger, private useCoolFeature: boolean) { + this.heroes = HEROES; + } + + getHeroes() { + let msg = this.useCoolFeature ? 'the cool new way' : 'the old way'; + this.logger.log('Getting heroes ...' + msg) + return this.heroes; + } + } + ``` + The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a + simple flag. + + We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user. + We'll' build up to that result, beginning with our definition of the factory function: + ``` + let heroServiceFactory = (logger: Logger, userService: UserService) => { + return new HeroService(logger, userService.user.isSpecial); + } + ``` +.l-sub-section + :markdown + The factory takes two parameters: the logger service and a user service. + The logger we pass straight to the constructor as we did before. + + We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true, + a fact we can't know until runtime. +:markdown + We use dependency injection everywhere so of course the factory function depends on + two injected services: `Logger` and `UserService`. + We declare those requirements in our provider definition object: + ``` + let heroServiceDefinition = { + useFactory: heroServiceFactory, + deps: [Logger, UserService] + }; + ``` +.l-sub-section + :markdown + The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`. + + The `deps` property is an array of provider mapping tokens. + The `Logger` and `UserService` classes serve as tokens for their own class provider mappings. +:markdown + Finally, we create the mapping and adjust the bootstrapping to include that mapping in its provider configuration. + ``` + let heroServiceMapping = provide(HeroService, heroServiceDefinition); + + bootstrap(AppComponent, [heroServiceMapping, Logger, UserService]); + ``` + ### String tokens + + Sometimes we have an object dependency rather than a class dependency. + + Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint. + These configuration objects aren't always instances of a class. They're just objects ... like this one: + ``` + let config = { + apiEndpoint: 'api.heroes.com', + title: 'The Hero Employment Agency' + }; + ``` + We'd like to make this `config` object available for injection. + We know we can register an object with a "Value Provider". But what do we use for the token? + + Until now, we've always had a class to use as the token for mapping. + The `HeroService` class was our token, whether we mapped it to another class, a value, or a factory provider. + This time we don't have a class. There is no `Config` class. + + Fortunately, a token can be either a JavaScript type (e.g. the class function) **or a string**. We'll map our configuration object + to a string! + ``` + bootstrap(AppComponent, [ + // other mappings // + provide('App.config', {useValue:config}) + ]); + ``` + Now let's update the `HeroesComponent` constructor so it can display the configured title. + Right now the constructor signature is + ``` + constructor(heroService: HeroService) + ``` + We might think we can write: + ``` + // FAIL! + constructor(heroService: HeroService, config: config) + ``` + That's not going to work. There is no type called `config` and we didn't register the `config` object under that name anyway. + We'll need a little help from another Angular decorator called `@Inject`. + ``` + import {Inject} from 'angular2/angulare2' + + constructor(heroService: HeroService, @Inject('app.config') config) + + ``` + +.l-main-section +:markdown + # Next Steps + We learned the basics of Angular Dependency Injection in this chapter. + + The Angular Dependency Injection is more capable than we've described. + We can learn more about its advanced features, beginning with its support for + a hierarchy of nested injectors in the next + [Dependency Injection chapter](./hierarchical-dependency-injection.html) + +.l-main-section + +:markdown + ### Appendix: Why we recommend one class per file + Developers expect one class per file. Multiple classes per file is confusing and is best avoided. + If we define every class in its own file, there is nothing in this note to worry about. + Move along! + + If we scorn this advice + and we add our `HeroService` class to the `HeroesComponent` file anyway, + **define the `HeroesComponent` last!** + If we put it define component before the service, + we'll get a runtime null reference error. + + To understand why, paste the following incorrect, ultra-simplified rendition of these two + classes into the [TypeScript playground](http://www.typescriptlang.org/Playground). + + ``` + class HeroesComponent { + static $providers=[HeroService] + } + + class HeroService { } + + alert(HeroesComponent.$providers) + ``` +.l-sub-section + :markdown + The `HeroService` is incorrectly defined below the `HeroComponent`. + + The `$providers` static property represents the metadata about the injected `HeroService` + that TypeScript compiler would add to the component class. + + The `alert` simulates the action of the Dependency Injector at runtime + when it attempts to create a `HeroesComponent`. +:markdown + Run it. The alert appears but displays nothing. + This is the equivalent of the null reference error thrown at runtime. + + We understand why when we review the generated JavaScript which looks like this: + ``` + var HeroesComponent = (function () { + function HeroesComponent() { + } + HeroesComponent.$providers = [HeroService]; + return HeroesComponent; + })(); + + var HeroService = (function () { + function HeroService() { + } + return HeroService; + })(); + + alert(HeroesComponent.$providers); + ``` + + Notice that the TypeScript compiler turns classes into function expressions + assigned to variables. The value of the captured `HeroService` variable is undefined + when the `$providers` array is assigned. The `HeroService` variable gets its value too late + to be captured. + + Reverse the order of class definition so that the `HeroService` + appears before the `HeroesComponent` that requires it. + Run again. This time the alert displays the `HeroService` function definition. + + If we insist on defining the `HeroService` in the same file and insist on + defining the component first, Angular offers a way to make that work. + The `forwardRef()` method let's us reference a class + before it has been defined. + Learn more about this problem and the `forwardRef()` + in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html). diff --git a/public/docs/ts/latest/tutorial/index.jade b/public/docs/ts/latest/tutorial/index.jade index 7d914c70fe..d08c6605fd 100644 --- a/public/docs/ts/latest/tutorial/index.jade +++ b/public/docs/ts/latest/tutorial/index.jade @@ -23,6 +23,8 @@ include ../../../../_includes/_util-fns Angular can do whatever we need it to do. We'll be covering a lot of ground at an introductory level but we’ll find plenty of links to chapters with greater depth. + + [Run the live example](/resources/live-examples/tutorial/ts/plnkr.html). .l-main-section :marked @@ -50,7 +52,7 @@ figure.image-display "Magneta" as the selected hero. figure.image-display - img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app") + img(src='/resources/images/devguide/toh/heroes-list-3.png' alt="Output of heroes list app") :marked We click a different hero and the readonly mini-detail beneath the list reflects our new choice. @@ -74,7 +76,7 @@ figure.image-display ## How We Roll We’ll build this Tour of Heroes together, step by step. - We'll motiviate each step with a requirement that we've + We'll motivate each step with a requirement that we've met in countless applications. Everything has a reason. And we’ll meet many of the core fundamentals of Angular along the way. diff --git a/public/docs/ts/latest/tutorial/toh-pt1.jade b/public/docs/ts/latest/tutorial/toh-pt1.jade index d357cb23b2..e0dd49cc1a 100644 --- a/public/docs/ts/latest/tutorial/toh-pt1.jade +++ b/public/docs/ts/latest/tutorial/toh-pt1.jade @@ -1,51 +1,43 @@ include ../../../../_includes/_util-fns -.l-main-section +:marked + # Once Upon a Time + + Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends. + + Follow the "QuickStart" steps. They provide the prerequisites, the folder structure, + and the core files for our Tour of Heroes. + + Copy the "QuickStart" code to a new folder and rename the folder `angular2-tour-of-heroes`. + We should have the following structure: + +code-example(format=""). + angular2-tour-of-heroes + ├── node_modules + ├── app + | ├── app.component.ts + | └── boot.ts + ├── index.html + ├── tsconfig.json + └── package.json + +:marked + ## Keep the app transpiling and running + We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing + +code-example(format="" language="bash"). + npm run go + +:marked + This command starts the server, launches the app in a browser, + and keeps the app running while we continue to build the Tour of Heroes. + +.l-sub-section :marked - # Once Upon a Time - - Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends. - - Follow the "QuickStart" steps. They provide the prerequisites, the folder structure, - and the core files for our Tour of Heroes. - - Copy the "QuickStart" code to a new folder and rename the folder `angular2-tour-of-heroes`. - We should have the following structure: - - code-example. - angular2-tour-of-heroes - ├── node_modules - ├── src - | ├── app - | | └── app.ts - | ├── index.html - | └── tsconfig.json - └── package.json - - :marked - ### Keep the app running - Start the TypeScript compiler and have it watch for changes in one terminal window by typing - - pre.prettyprint.lang-bash - code npm run tsc - - :marked - Now open another terminal window and start the server by typing - - pre.prettyprint.lang-bash - code npm start - - :marked - This command starts the server, launches the app in a browser, - and keeps the app running while we continue to build the Tour of Heroes. - - .l-sub-section - :marked - These two steps watch all project files. They recompile TypeScript files and re-run - the app when any file changes. - If the watchers fail to detect renamed or new files, - stop these commands in each terminal by typing `CTRL+C` and then re-run them. - + These two steps watch all project files. They recompile TypeScript files and re-run + the app when any file changes. + If the watchers fail to detect renamed or new files, + stop these commands in each terminal by typing `CTRL+C` and then re-run them. .l-main-section :marked ## Show our Hero @@ -54,87 +46,70 @@ include ../../../../_includes/_util-fns Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property for a hero named "Windstorm". - ``` - class AppComponent { - public title = 'Tour of Heroes'; - public hero = 'Windstorm'; - } - ``` + +makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'app-component-1', 'app.component.ts (AppComponent class)')(format=".") :marked Now we update the template in the `@Component` decoration with data bindings to these new properties. - code-example(format=""). - template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>' + +makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero') + :marked The browser should refresh and display our title and hero. The double curly braces tell our app to read the `title` and `hero` properties from the component and render them. This is the "interpolation" form of one-way data binding. - .l-sub-section - :marked - Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html). +.l-sub-section :marked - ### Hero object + Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html). +:marked + ### Hero object - At the moment, our hero is just a name. Our hero needs more properties. - Let's convert the `hero` from a literal string to a class. + At the moment, our hero is just a name. Our hero needs more properties. + Let's convert the `hero` from a literal string to an interface. - Create a `Hero` class with `id` and `name` properties. - Keep this near the top of the `app.ts` file for now. + Create a `Hero` interface with `id` and `name` properties. + Keep this near the top of the `app.component.ts` file for now. - ``` - class Hero { - id: number; - name: string; - } - ``` ++makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'hero-interface-1', 'app.component.ts (Hero interface)')(format=".") - Now that we have a `Hero` class, let’s refactor our component’s `hero` property to be of type `Hero`. - Then initialize it with an id of `1` and the name, "Windstorm". - - ``` - public hero: Hero = { - id: 1, - name: 'Windstorm' - }; - ``` - - Because we changed the hero from a string to an object, - we update the binding in the template to refer to the hero’s `name` property. - - code-example(format=""). - template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>' +.l-sub-section :marked - The browser refreshes and continues to display our hero’s name. + Why an interface and not a class? The net result is that either option will allow us to check the types. The answer here lies in how we intend to use the Hero. We want something to check the types, so either option will suffice. If we wanted to create an instance of a Hero, a class may be more appropriate since we could add logic to a Hero constructor. But our scenario is for type checking, so the interface is adequate. The driving reason however, that leads us to a Hero interface is that the interface when transpiled from TypeScript to JavaScript produces no ES5 code. None at all. While a TypeScript class does generate ES5 code. For these reasons we choose an interface here. - ### Adding more HTML - Displaying a name is good, but we want to see all of our hero’s properties. - We’ll add a `
    ` for our hero’s `id` property and another `
    ` for our hero’s `name`. +:marked + Now that we have a `Hero` interface, let’s refactor our component’s `hero` property to be of type `Hero`. + Then initialize it with an id of `1` and the name, "Windstorm". - code-example(format="linenums"). - template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>' - :marked - Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template. ++makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'hero-property-1', 'app.component.ts (Hero property)')(format=".") - ### Multi-line template strings +:marked + Because we changed the hero from a string to an object, + we update the binding in the template to refer to the hero’s `name` property. - We could make a more readable template with string concatenation - but that gets ugly fast, it is harder to read, and - it is easy to make a spelling error. Instead, - let’s take advantage of the template strings feature - in ES2015 and TypeScript to maintain our sanity. ++makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero-2') +:marked + The browser refreshes and continues to display our hero’s name. - Change the quotes around the template to back-ticks and - put the `

    `, `

    ` and `
    ` elements on their own lines. + ### Adding more HTML + Displaying a name is good, but we want to see all of our hero’s properties. + We’ll add a `
    ` for our hero’s `id` property and another `
    ` for our hero’s `name`. - code-example(format="linenums"). - template:` - <h1>{{title}}</h1> - <h2>{{hero.name}} details!</h2> - <div><label>id: </label>{{hero.id}}</div> - <div><label>name: </label>{{hero.name}}</div> - ` ++makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero-properties') +:marked + Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template. + + ### Multi-line template strings + + We could make a more readable template with string concatenation + but that gets ugly fast, it is harder to read, and + it is easy to make a spelling error. Instead, + let’s take advantage of the template strings feature + in ES2015 and TypeScript to maintain our sanity. + + Change the quotes around the template to back-ticks and + put the `

    `, `

    ` and `
    ` elements on their own lines. + ++makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)') .callout.is-important header A back-tick is not a single quote @@ -153,16 +128,8 @@ include ../../../../_includes/_util-fns We want to be able to edit the hero name in a textbox. Refactor the hero name `