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
This commit is contained in:
John Papa 2015-11-15 18:04:43 -08:00 committed by Ward Bell
parent daffdb73fd
commit ab5390a51e
46 changed files with 3314 additions and 545 deletions

View File

@ -4,3 +4,4 @@ package.json
karma.conf.js
karma-test-shim.js
tsconfig.json
npm-debug*.log

View File

@ -0,0 +1,53 @@
// #docregion show-hero
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
// #enddocregion show-hero
// #docregion show-hero-2
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
// #enddocregion show-hero-2
// #docregion show-hero-properties
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
// #enddocregion show-hero-properties
// #docregion multi-line-strings
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div><label>name: </label>{{hero.name}}</div>
`
// #enddocregion multi-line-strings
// #docregion editing-Hero
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<div><input value="{{hero.name}}" placeholder="name"></div>
</div>
`
// #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

View File

@ -0,0 +1,29 @@
// #docregion pt1
import {Component} from 'angular2/core';
interface Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<div><input [(ngModel)]="hero.name" placeholder="name"></div>
</div>
`
})
export class AppComponent {
public title = 'Tour of Heroes';
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
// #enddocregion pt1

View File

@ -0,0 +1,6 @@
// #docregion pt1
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);
// #enddocregion pt1

View File

@ -0,0 +1,99 @@
// #docregion ng-for
<li *ngFor="#hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion ng-for
// #docregion heroes-styled
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
// #enddocregion heroes-styled
// #docregion selectedHero-click
<li *ngFor="#hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion selectedHero-click
// #docregion selectedHero-details
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
// #enddocregion selectedHero-details
// #docregion ng-if
<div *ngIf="selectedHero">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
</div>
// #enddocregion ng-if
// #docregion hero-array-1
public heroes = HEROES;
// #enddocregion hero-array-1
// #docregion heroes-template-1
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<!-- each hero goes here -->
</li>
</ul>
// #enddocregion heroes-template-1
// #docregion heroes-ngfor-1
<li *ngFor="#hero of heroes">
// #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
<li *ngFor="#hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion class-selected-2

View File

@ -0,0 +1,74 @@
// #docregion pt2
import {Component} from 'angular2/core';
interface Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template:`
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
</div>
`,
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

View File

@ -0,0 +1,6 @@
// #docregion pt1
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);
// #enddocregion pt1

View File

@ -0,0 +1,14 @@
<!-- #docregion -->
template: `
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ng-for="#hero of heroes"
[ng-class]="getSelectedClass(hero)"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}</a>
</li>
</ul>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,

View File

@ -0,0 +1,34 @@
// #docregion template
@Component({
selector: 'my-hero-detail',
template: `
<div *ng-if="hero">
<h2>{{selected.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ng-model)]="hero.name" placeholder="name"/>
</div>
</div>
`,
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
})
// #enddocregion template
// #docregion inputs
@Component({
selector: 'my-hero-detail',
template: `
<div *ng-if="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ng-model)]="hero.name" placeholder="name"/>
</div>
</div>
`,
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
inputs: ['hero']
})
// #enddocregion inputs

View File

@ -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

View File

@ -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 its id }
// #enddocregion onit-id-param
// #docregion onit-hero-id
onInit() {
if (!this.hero) {
let id = +this._routeParams.get('id');
// TODO: get the hero using its 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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,186 @@
<!-- #docregion head -->
<head>
<base href="/src/" />
<!-- #docregion stylesheet -->
<link rel="stylesheet" href="styles.css">
<!-- #enddocregion stylesheet -->
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="../node_modules/angular2/bundles/router.dev.js"></script>
</head>
<!-- #enddocregion head -->
<!-- #docregion bootstrap -->
System.import('app/bootstrap');
<!-- #enddocregion bootstrap -->
<!-- #docregion title -->
import {Component} from 'angular2/angular2';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
`
})
export class AppComponent {
public title = 'Tour of Heroes';
}
<!-- #enddocregion title -->
<!-- #docregion routes-title -->
import {Component} from 'angular2/angular2';
import {RouteConfig} from 'angular2/router';
<!-- #docregion import-app-routes -->
import {APP_ROUTES} from './route.config';
<!-- #enddocregion import-app-routes -->
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
`
})
@RouteConfig(APP_ROUTES)
export class AppComponent {
public title = 'Tour of Heroes';
}
<!-- #enddocregion routes-title -->
<!-- #docregion router-outlet -->
template: `
<h1>{{title}}</h1>
<router-outlet></router-outlet>
`
<!-- #enddocregion router-outlet -->
<!-- #docregion simple-dashboard-component -->
import {Component} from 'angular2/angular2';
@Component({
selector: 'my-dashboard',
template: `
<h1>Dashboard Goes Here</h1>
`
})
export class DashboardComponent {}
<!-- #enddocregion simple-dashboard-component -->
<!-- #docregion router-link -->
template: `
<h1>{{title}}</h1>
<a [router-link]="/Dashboard">Dashboard</a>
<a [router-link]="/Heroes">Heroes</a>
<router-outlet></router-outlet>
`,
<!-- #enddocregion router-link -->
<!-- #docregion ng-for -->
template: `
<h3>Top Heroes</h3>
<div>
<div *ng-for="#hero of heroes">
<div>
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
`
<!-- #enddocregion ng-for -->
<!-- #docregion css -->
[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; }
}
<!-- #enddocregion css -->
<!-- #docregion template-styled -->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<div *ng-for="#hero of heroes | slice:0:4" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
<!-- #enddocregion template-styled -->
<!-- #docregion styled-nav-links -->
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<a [router-link]="['/' + routes.dashboard.as]"
class="router-link">Dashboard</a>
<a [router-link]="['/' + routes.heroes.as]"
class="router-link">Heroes</a>
<router-outlet></router-outlet>
`,
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]
})
<!-- #docregion styled-nav-links -->
<!-- #docregion select-hero-click-event -->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<div *ng-for="#hero of heroes | slice:0:4" class="col-1-4"
(click)="gotoDetail(hero)">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
<!-- #docregion select-hero-click-event -->
<!-- #docregion display-hero-name -->
<div>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ng-for="#hero of heroes"
[ng-class]="getSelectedClass(hero)"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}</a>
</li>
</ul>
<div *ng-if="selectedHero">
<h2>{{selectedHero.name | uppercase}} is my hero</h2>
<button (click)="gotoDetail()">View Details</button>
</div>
</div>
<!-- #enddocregion display-hero-name -->
<!-- #docregion basic-styles -->
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}
<!-- #enddocregion basic-styles -->

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -0,0 +1,6 @@
typings
**/*.js
**/*.map
node_modules
jspm_packages
bower_components

View File

@ -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: `
<h1>{{title}}</h1>
<a [routerLink]="['Dashboard']">Dashboard</a>
<a [routerLink]="['Heroes']">Heroes</a>
<router-outlet></router-outlet>
`,
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';
}

View File

@ -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
]);

View File

@ -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; }
}

View File

@ -0,0 +1,8 @@
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<div *ngFor="#hero of heroes | slice:0:4" class="col-1-4" (click)="gotoDetail(hero)">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>

View File

@ -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 }]);
}
}

View File

@ -0,0 +1,10 @@
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
<button (click)="goBack()">Back</button>
</div>

View File

@ -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();
}
}

View File

@ -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]);
}
}

View File

@ -0,0 +1,4 @@
export interface Hero {
id: number;
name: string;
}

View File

@ -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; }

View File

@ -0,0 +1,14 @@
<div>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} is my hero</h2>
<button (click)="gotoDetail()">View Details</button>
</div>
</div>

View File

@ -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; }
}

View File

@ -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"}
];

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<base href="/"/>
<link rel="stylesheet" href="styles.css">
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="node_modules/angular2/bundles/router.dev.js"></script>
<script>
System.config({
packages: {'app': {defaultExtension: 'js'}}
});
System.import('app/boot').catch(console.log.bind(console));
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,8 @@
{
"description": "Tour of Heroes",
"files":[
"!**/*.d.ts",
"!**/*.js"
],
"tags": ["tutorial", "tour", "heroes"]
}

View File

@ -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}

View File

@ -523,8 +523,7 @@ code-example(format="").
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?

View File

@ -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.
<a name="why-di"></a>
.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:
<a name="ctor-injection"></a>
```
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.
<a name="bootstrap"></a>
Angular creates an application-wide injector for us during the bootstrap process.
```
bootstrap(HeroesComponent);
```
Lets 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]);
```
Thats 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();
}
```
<a name="di-metadata"></a>
.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 ...
```
<my-heroes></heroes>
```
... 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.
Well 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
<a name="providers"></a>
## 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
<a name="forward-ref"></a>
: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).

View File

@ -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.
<a name="why-di"></a>
.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:
<a name="ctor-injection"></a>
```
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.
<a name="bootstrap"></a>
Angular creates an application-wide injector for us during the bootstrap process.
```
bootstrap(HeroesComponent);
```
Lets 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]);
```
Thats 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();
}
```
<a name="di-metadata"></a>
.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 ...
```
<my-heroes></heroes>
```
... 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.
Well 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
<a name="providers"></a>
## 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
<a name="forward-ref"></a>
: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).

View File

@ -24,6 +24,8 @@ include ../../../../_includes/_util-fns
We'll be covering a lot of ground at an introductory level but well find plenty of links
to chapters with greater depth.
[Run the live example](/resources/live-examples/tutorial/ts/plnkr.html).
.l-main-section
:marked
## The End Game
@ -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
Well 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 well meet many of the core fundamentals of Angular along the way.

View File

@ -1,6 +1,5 @@
include ../../../../_includes/_util-fns
.l-main-section
:marked
# Once Upon a Time
@ -12,28 +11,22 @@ include ../../../../_includes/_util-fns
Copy the "QuickStart" code to a new folder and rename the folder `angular2-tour-of-heroes`.
We should have the following structure:
code-example.
code-example(format="").
angular2-tour-of-heroes
├── node_modules
├── src
| ├── app
| | └── app.ts
| ├── index.html
| └── tsconfig.json
├── app
| ├── app.component.ts
| └── boot.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
## 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
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
code-example(format="" language="bash").
npm run go
:marked
This command starts the server, launches the app in a browser,
@ -45,7 +38,6 @@ include ../../../../_includes/_util-fns
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,18 +46,13 @@ 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: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero}} details!&lt/h2>'
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero')
:marked
The browser should refresh and display our title and hero.
@ -78,33 +65,28 @@ include ../../../../_includes/_util-fns
### 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.
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, lets refactor our components `hero` property to be of type `Hero`.
.l-sub-section
:marked
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.
:marked
Now that we have a `Hero` interface, lets refactor our components `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'
};
```
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'hero-property-1', 'app.component.ts (Hero property)')(format=".")
:marked
Because we changed the hero from a string to an object,
we update the binding in the template to refer to the heros `name` property.
code-example(format="").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>'
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'show-hero-2')
:marked
The browser refreshes and continues to display our heros name.
@ -112,8 +94,7 @@ include ../../../../_includes/_util-fns
Displaying a name is good, but we want to see all of our heros properties.
Well add a `<div>` for our heros `id` property and another `<div>` for our heros `name`.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/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.
@ -128,13 +109,7 @@ include ../../../../_includes/_util-fns
Change the quotes around the template to back-ticks and
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/div>
`
+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 `<label>` with `<label>` and `<input>` elements as shown below:
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/div>
&lt/div>
`
+makeExample('toh-1/ts/app/app.component.snippets.pt1.ts', 'editing-Hero', 'app.component.ts (input element)')
:marked
We see in the browser that the heros name does appear in the `<input>` textbox.
But something doesnt feel right.
@ -176,88 +143,21 @@ include ../../../../_includes/_util-fns
and see those changes wherever we bind to the heros name.
In short, we want two-way data binding.
Lets update the template to use the **`ng-model`** built-in directive for two-way binding.
Lets update the template to use the **`ngModel`** built-in directive for two-way binding.
.l-sub-section
:marked
Learn more about `ng-model` in the [Template Syntax chapter](../guide/template-syntax.html#ng-model)
Learn more about `ngModel` in the [Template Syntax chapter](../guide/template-syntax.html#ng-model)
:marked
Replace the `<input>` with the following HTML
code-example(language="html").
&lt;input [(ng-model)]="hero.name" placeholder="name">
&lt;input [(ngModel)]="hero.name" placeholder="name">
:marked
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
Lets fix that next.
.l-main-section
:marked
## Declaring Template Directives
We added the `ng-model` directive but we didn't tell Angular about it.
A component must disclose every directive that appears in its template.
Lets first gain access to the `NgModel` directive class by importing it from Angular as shown below:
````
import {bootstrap, Component, NgModel} from 'angular2/angular2';
```
Now tell the component that we will use the `ng-model` directive in the template
by adding the `directives` property to the `@Component` decoration
immediately below the `template` string:
```
directives: [NgModel]
```
The `directives` property is an array holding all directive classes that
are used by the components template.
Unfortunately when we view the app in the browser we still have an error:
code-example(language="html").
EXCEPTION: No value accessor for ' ' in [null]
:marked
Apparently declaring the `NgModel` is not quite enough.
## Declare Multiple Form Directives
We learned from our latest error message that we cant import the `NgModel` alone.
We need additional directives to enable two-way data binding with `NgModel`.
We could hunt them down and add each of them to the `directives` array one by one.
That's painful. No one wants to remember all of the necessary directives and
type them correctly. Fortunately, there is a shortcut.
The `ng-model` directive is one of many Forms directives which happen to be
bundled in a convenient array called `FORM_DIRECTIVES`.
.l-sub-section
:marked
Learn more about Angular Forms in the [Forms chapter](../guide/forms.html)
:marked
Lets forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
```
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
```
Now we tell the component that our template can use `FORM_DIRECTIVES`
by updating the `directives` property of the `@Component` decorator.
```
directives: [FORM_DIRECTIVES]
```
The browser refreshes. We see our hero again. We can edit the heros name and
see the changes reflected immediately in the `<h2>`.
### Bundled directives
Angular bundled the Form-related directives together in a convenient `FORM_DIRECTIVES` array.
That's all we need to remember to light up our template.
We may wish to use this trick ourselves someday.
We too can bundle a collection of directives in an array, give it a catchy name,
and plug that array into the `directives` property.
.l-main-section
:marked
## The Road Weve Travelled
@ -267,43 +167,12 @@ include ../../../../_includes/_util-fns
to display the application title and properties of a `Hero` object.
* We wrote a multi-line template using ES2015s template strings to make our template readable.
* We can both display and change the heros name after adding a two-way data binding to the `<input>` element
using the built-in `ng-model` directive.
* The `ng-model` directive also propagates changes to every other binding of the `hero.name`.
* We declared our use of `NgModel` and other Form directives
by setting the component's `directives` metadata property to the `FORMS_DIRECTIVES` array.
using the built-in `ngModel` directive.
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
Here's the complete `app.ts` as it stands now:
Here's the complete `app.component.ts` as it stands now:
code-example(format="linenums").
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
class Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput [(ng-model)]="hero.name" placeholder="name">&lt/div>
&lt/div>
`,
directives: [FORM_DIRECTIVES]
})
class AppComponent {
public title = 'Tour of Heroes';
public hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
bootstrap(AppComponent);
+makeExample('toh-1/ts/app/app.component.ts', 'pt1', 'app.component.ts')
.l-main-section
:marked

View File

@ -1,6 +1,5 @@
include ../../../../_includes/_util-fns
.l-main-section
:marked
# It Takes Many Heroes
Our story needs more heroes.
@ -18,27 +17,22 @@ include ../../../../_includes/_util-fns
lets verify we have the following structure after [Part 1](./toh-pt1.html).
If not, well need to go back to Part 1 and figure out what we missed.
code-example.
code-example(format="").
angular2-tour-of-heroes
├── node_modules
├── src
| ├── app
| | └── app.ts
| ├── index.html
| └── tsconfig.json
├── app
| ├── app.component.ts
| └── boot.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
### 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
pre.prettyprint.lang-bash
code npm start
code-example(format="." language="bash").
npm run go
:marked
This will keep the application running while we continue to build the Tour of Heroes.
@ -47,21 +41,11 @@ include ../../../../_includes/_util-fns
:marked
## Displaying Our Heroes
### Creating heroes
Lets create an array of ten heroes at the bottom of `app.ts`.
```
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" }
];
```
Lets create an array of ten heroes at the bottom of `app.component.ts`.
+makeExample('toh-2/ts/app/app.component.ts', 'hero-array', 'app.component.ts (Hero array)')
:marked
The `HEROES` array is of type `Hero`.
We are taking advantage of the `Hero` class we coded previously to create an array of our heroes.
We aspire to get this list of heroes from a web service, but lets take small steps
@ -69,9 +53,10 @@ include ../../../../_includes/_util-fns
### Exposing heroes
Lets create a public property in `AppComponent` that exposes the heroes for binding.
```
public heroes = HEROES;
```
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'hero-array-1', 'app.component.ts (Hero array property)')
:marked
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
.l-sub-section
:marked
@ -83,84 +68,52 @@ include ../../../../_includes/_util-fns
### Displaying heroes in a template
Our component has `heroes`. Lets create an unordered list in our template to display them.
Well insert the following chunk of HTML below the title and above the hero details.
```
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<!-- each hero goes here -->
</li>
</ul>
```
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-template-1', 'app.component.ts (Heroes template)')
:marked
Now we have a template that we can fill with our heroes.
### Listing heroes with ng-for
### Listing heroes with ngFor
We want to bind the array of `heroes` in our component to our template, iterate over them,
and display them individually.
Well need some help from Angular to do this. Lets do this step by step.
First modify the `<li>` tag by adding the built-in directive `*ng-for`.
```
<li *ng-for="#hero of heroes">
```
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-ngfor-1', 'app.component.ts (ngFor)')
.alert.is-critical
:marked
The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax.
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
.l-sub-section
:marked
The (`*`) prefix to `ng-for` indicates that the `<li>` element and its children
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
constitute a master template.
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
The `ngFor` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
and stamps out instances of this template.
The quoted text assigned to `ng-for` means
The quoted text assigned to `ngFor` means
“*take each hero in the `heroes` array, store it in the local `hero` variable,
and make it available to the corresponding template instance*”.
The `#` prefix before "hero" identifies the `hero` as a local template variable.
We can reference this variable within the template to access a heros properties.
Learn more about `ng-for` and local template variables in the
Learn more about `ngFor` and local template variables in the
[Template Syntax chapter](../guide/template-syntax.html#ng-for).
:marked
With this background in mind, we now insert some content between the `<li>` tags
that uses the `hero` template variable to display the heros properties.
code-example(format="linenums" language="html").
&lt;li *ng-for="#hero of heroes">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}
&lt;/li>
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'ng-for', 'app.component.ts (ngFor template)')(format=".")
:marked
### Declaring ng-for
When we view the running app in the browser we see nothing … no heroes.
We open the developer tools and see an error in the console.
code-example(language="html" ).
EXCEPTION:
Can't bind to 'ngForOf' since it isn't a known property of the '&lt;template>' element and
there are no matching directives with a corresponding property
:marked
Thankfully we have a clear error message that indicates where we went wrong.
We used `ng-for` in the template but we didnt tell the component about it.
From Angular's perspective, `ng-for` is a meaningless attribute.
When it tries to render the view, it doesnt recognize `ng-for` and gives up.
We need to say “*hey component, Im going to use this NgFor directive. OK?*”
To that end, we first import the `NgFor` symbol
```
import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';
```
and then declare `NgFor` to be one of the views directives in the `@Component` decorator.
```
directives: [FORM_DIRECTIVES, NgFor]
```
After the browser refreshes, we see a list of heroes!
When the browser refreshes, we see a list of heroes!
### Styling our heroes
Our list of heroes looks pretty bland.
@ -168,24 +121,10 @@ include ../../../../_includes/_util-fns
Lets add some styles to our component by setting the `styles` property on the `@Component` decorator
to the following CSS classes:
```
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; }
`],
```
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'styles-1', 'app.component.ts (Styling)')
:marked
Notice that we again use the back-tick notation for multi-line strings.
When we assign styles to a component they are scoped to that specific component.
@ -193,13 +132,8 @@ include ../../../../_includes/_util-fns
Our template for displaying the heroes should now look like this:
code-example(format="linenums").
&lt;h2>My Heroes&lt;/h2>
&lt;ul class="heroes">
&lt;li *ng-for="#hero of heroes">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}
&lt;/li>
&lt;/ul>
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'heroes-styled', 'app.component.ts (Styled heroes)')
:marked
Our styled list of heroes should look like this:
@ -212,7 +146,7 @@ include ../../../../_includes/_util-fns
We have a list of heroes and we have a single hero displayed in our app.
The list and the single hero are not connected in any way.
We want the user to select a hero from our list, and have the selected hero appear in the details view.
This UI pattern is widely known as “master-detail”.
This UI pattern is widely known as "master-detail".
In our case, the master is the heroes list and the detail is the selected hero.
Lets connect the master to the detail through a `selectedHero` component property bound to a click event.
@ -220,19 +154,17 @@ include ../../../../_includes/_util-fns
### Click event
We modify the `<li>` by inserting an Angular event binding to its click event.
code-example(format="linenums").
&lt;li *ng-for="#hero of heroes" (click)="onSelect(hero)">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}
&lt;/li>
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selectedHero-click', 'app.component.ts (Capturing the click event)')
:marked
Focus on the event binding
pre.prettyprint.lang-bash
code (click)="onSelect(hero)">
code-example(format="." language="bash").
(click)="onSelect(hero)"
:marked
The parenthesis identify the `<li>` elements `click` event as the target.
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
passing the local template variable `hero` as an argument.
Thats the same `hero` variable we defined previously in the `ng-for`.
Thats the same `hero` variable we defined previously in the `ngFor`.
.l-sub-section
:marked
Learn more about Event Binding in the [Templating Syntax chapter](../guide/template-syntax.html#event-binding).
@ -248,31 +180,24 @@ include ../../../../_includes/_util-fns
### Expose the selected hero
We no longer need the static `hero` property of the `AppComponent`.
**Replace** it with this simple `selectedHero` property:
```
public selectedHero: Hero;
```
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selected-hero-1', 'app.component.ts (selectedHero)')
:marked
Weve decided that none of the heroes should be selected before the user picks a hero so
we wont initialize the `selectedHero` as we were doing with `hero`.
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
```
onSelect(hero: Hero) { this.selectedHero = hero; }
```
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'on-select-1', 'app.component.ts (onSelect)')
:marked
We will be showing the selected hero's details in our template.
At the moment, it is still referring to the old `hero` property.
Lets fix the template to bind to the new `selectedHero` property.
code-example(format="linenums").
&lt;h2>{{selectedHero.name}} details!&lt;/h2>
&lt;div>&lt;label>id: &lt;/label>{{selectedHero.id}}&lt;/div>
&lt;div>
&lt;label>name: &lt;/label>
&lt;input [(ng-model)]="selectedHero.name" placeholder="name">&lt;/input>
&lt;/div>
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'selectedHero-details', 'app.compontent.ts (Binding to the selectedHero\'s name)')
:marked
### Hide the empty detail with ng-if
### Hide the empty detail with ngIf
When our app loads we see a list of heroes, but a hero is not selected.
The `selectedHero` is `undefined`.
@ -288,52 +213,32 @@ include ../../../../_includes/_util-fns
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
We wrap the HTML hero detail content of our template with a `<div>`.
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'ng-if', 'app.component.ts (ngIf)')
code-example(format="linenums").
&lt;div *ng-if="selectedHero">
&lt;h2>{{selectedHero.name}} details!&lt;/h2>
&lt;div>&lt;label>id: &lt;/label>{{selectedHero.id}}&lt;/div>
&lt;div>
&lt;label>name: &lt;/label>
&lt;input [(ng-model)]="selectedHero.name" placeholder="name">&lt;/input>
&lt;/div>
&lt;/div>
.alert.is-critical
:marked
Remember that the leading asterisk (`*`) in front of `ng-if` is
Remember that the leading asterisk (`*`) in front of `ngIf` is
a critical part of this syntax.
:marked
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
There will be no hero detail elements and no bindings to worry about.
When the user picks a hero, `selectedHero` becomes "truthy" and
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
.l-sub-section
:marked
`ng-if` and `ng-for` are called “structural directives” because they can change the
`ngIf` and `ngFor` are called “structural directives” because they can change the
structure of portions of the DOM.
In other words, they give structure to the way Angular displays content in the DOM.
Learn more about `ng-if`, `ng-for` and other structural directives in the
Learn more about `ngIf`, `ngFor` and other structural directives in the
[Template Syntax chapter](../guide/template-syntax.html#directives).
:marked
We learned previously with `NgFor` that we must declare every directive we use in the components `@Component` decorator.
Lets do that again for `NgIf`.
Add the `NgIf` symbol to our imports at the top of our `app.ts` file, keeping them sorted
alphabetically to make them easier to find:
```
import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2';
```
:marked
Now add `NgIf` to the directives array in the `@Component` decorator:
```
directives: [FORM_DIRECTIVES, NgFor, NgIf]
```
The browser refreshes and we see the list of heroes but not the selected hero detail.
The `ng-if` keeps it out of the DOM as long as the `selectedHero` is undefined.
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
When we click on a hero in the list, the selected hero displays in the hero details.
Everything is working as we expect.
@ -347,51 +252,23 @@ include ../../../../_includes/_util-fns
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
:marked
First well add a `getSelectedClass` method to the component that compares the current `selectedHero` to a hero parameter
and returns an object with a single key/value pair.
Well add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
Were saying “*apply the `selected` class if the heroes match, remove it if they dont*”.
Here is that method.
```
getSelectedClass(hero: Hero) {
return { 'selected': hero === this.selectedHero };
}
```
What do we do with this method and its peculiar result?
### ng-class
Well add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
Its no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
to add or remove the `selected` class to each heros display.
code-example(format="linenums").
&lt;li *ng-for="#hero of heroes"
[ng-class]="getSelectedClass(hero)"
(click)="onSelect(hero)">
&lt;span class="badge">{{hero.id}}&lt;/span> {{hero.name}}
&lt;/li>
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'class-selected-1', 'app.component.ts (Setting the CSS class)')(format=".")
:marked
Notice in the template that the `ng-class` name is surrounded in square brackets (`[]`).
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
This is the syntax for a Property Binding, a binding in which data flows one way
from the data source (the `getSelectedClass`) to a property of the `ng-class` directive.
from the data source (the expression `hero === selectedHero`) to a property of `class`.
+makeExample('toh-2/ts/app/app.component.snippets.pt2.ts', 'class-selected-2', 'app.component.ts (Styling each hero)')(format=".")
.l-sub-section
:marked
Learn more about [ng-class](../guide/template-syntax.html#ng-class)
and [Property Binding](../guide/template-syntax.html#property-binding)
Learn more about [Property Binding](../guide/template-syntax.html#property-binding)
in the Template Syntax chapter.
:marked
We've added yet another new directive to our template that we have to import and declare
in the components `directives` array as weve done twice before.
```
import {bootstrap, Component,
FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
```
```
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]
```
:marked
The browser reloads our app.
We select a hero and the selection is clearly identified by the background color.
@ -401,30 +278,9 @@ include ../../../../_includes/_util-fns
:marked
We select a different hero and the tell-tale color switches to that hero.
## Declaring Built-In Directives
Here's the complete `app.component.ts` as it stands now:
Every time we used a directive, we imported it and declared it in the component.
We only used three directives but we can easily envision a component that uses many more.
The `directives` array grows quickly and the process of importing the directive and adding it to the array is tedious.
We can make this easier.
Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter?
The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more).
We didnt have to list them. We simply added the `FORM_DIRECTIVES` array to the components `directives` array.
The `NgClass`, `NgFor`, and `NgIf` are extremely common directives used by many components in many applications.
Fortunately they are all exported from Angular as part of the `CORE_DIRECTIVES` array.
Lets replace all of those separate import variables with `CORE_DIRECTIVES`:
```
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
```
Then replace `NgClass`, `NgFor`, and `NgIf` in the `directives` array with `CORE_DIRECTIVES`:
```
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
```
Everything still works and we have a convenient way to import and declare the most commonly used directives.
Cleaner code for the win!
+makeExample('toh-2/ts/app/app.component.ts', 'pt2', 'app.component.ts')
.l-main-section
:marked
@ -433,7 +289,7 @@ include ../../../../_includes/_util-fns
* Our Tour of Heroes now displays a list of selectable heroes
* We added the ability to select a hero and show the heros details
* We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a components template
* We learned how to use the built-in directives `ngIf` and `ngFor` in a components template
### The Road Ahead
Our Tour of Heroes has grown, but its far from complete.

View File

@ -0,0 +1,363 @@
include ../../../../_includes/_util-fns
:marked
# Shared Components and Services
Our app is growing.
Use cases are flowing in for reusing components, passing data to components, sharing the hero data, and preparing to retrieve the data asynchronously via a promise.
.l-main-section
:marked
## Reviewing Where We Left Off
Before we continue with our Tour of Heroes, lets verify we have the following structure. If not, well need to go back and follow the previous chapters.
code-example.
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 will keep the application running while we continue to build the Tour of Heroes.
## Making a Hero Detail Component
Our heroes list and our hero details are all in the same component. What if we want to reuse the hero details somewhere else in our app? This would be difficult since it is intermixed with the heroes list. Lets make this easier and separate the hero details into its own component to make this more reusable.
### Separating the Hero Detail Component
Well need a new file and a new component to host our hero details. Lets create a new file named `hero-detail.component.ts` with a component named `HeroDetailComponent`.
```
@Component({
selector: 'my-hero-detail'
})
export class HeroDetailComponent { }
```
We want to use `HeroDetailComponent` from our original `AppComponent`. Well need the selector name when we refer to it in `AppComponent`s template. We export the `HeroDetailComponent` here so we can later import it into our `AppComponent`, which well do after we finish creating our `HeroDetailComponent`.
We anticipate our template will contain multiple lines. So lets initialize the `template` property to an empty string between back-ticks.
```
@Component({
selector: 'my-hero-detail',
template: ``
})
export class HeroDetailComponent { }
```
Remember, we want to refer to our `HeroDetailComponent` in the `AppComponent`. This is why we export the `HeroDetailComponent` class.
#### Hero Detail Template
Our heroes and hero details are combined in one template in `AppComponent` so we need to separate them. Lets move the appropriate template content from `AppComponent` and paste it in the template property of `HeroDetailComponent`.
Lets also change the name of the property in the template from `selectedHero` to `hero`, as it is more appropriate for a reusable component.
+makeExample('toh-3/ts/app/hero-detail.snippets.pt3.ts', 'template')
:marked
Our `HeroDetailComponent` uses `ng-model` (which is in the `FORM_DIRECTIVES` array) and `ng-if` (which is in the `CORE_DIRECTIVES` array). We have to tell our component about these directives, so we declare them in the `directives` property of the `@Component` decorator.
Now our hero detail template exists only in our `HeroDetailComponent`.
#### Importing
Now that we have the foundation for the component, we need to make sure that we import everything we are using in the component. Lets add the following import statement to the top of our `hero-detail.component.ts` file to get the exports from Angular that we are using.
```
import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
```
#### Declaring our Hero
Our `HeroDetailComponent`s template refers to a hero, so lets add a property on the component to hold the hero.
```
export class HeroDetailComponent {
public hero: Hero;
}
```
Uh oh. We declare the `hero` property as being of type `Hero` but our `Hero` class is over in the `app.ts` file. We now have two components, each in their own file, that need to reference the `Hero` class. Lets solve this problem by removing the `Hero` class from `app.ts` and moving it to its own file named `hero.ts`.
```
export class Hero {
id: number;
name: string;
}
```
We export the `Hero` class from `hero.ts` because we will need to import it in both of our component files. Lets add the following import statement to the top of both `app.ts` and `hero.-detail.component.ts`.
```
import {Hero} from './hero';
```
Now we can also use the `Hero` class from other files by importing it.
#### Defining the Input for HeroDetailComponent
Our `HeroDetailComponent` needs to be told what hero to use. We have a `hero` property, but we need a way for the `AppComponent` to tell the `HeroDetailComponent` the hero it should use.
Lets declare the inputs for our component in the `@Component` decorators `inputs` property. We will set the input to the `hero` property so it matches the `hero` property on the `HeroDetailComponent`.
+makeExample('toh-3/ts/app/hero-detail.snippets.pt3.ts', 'inputs')
:marked
Now our `AppComponent`, or any component that refers to `HeroDetailComponent`, can tell the `HeroDetailComponent` which hero to use.
### Making AppComponent Refer to the HeroDetailComponent
Our `HeroDetailComponent` is ready, but we need to go back to the `AppComponent` and clean up some loose ends.
First we need to tell our `AppComponent` about our new component. Lets add an import statement so we can refer to the `HeroDetailComponent`.
```
import {HeroDetailComponent} from './hero-detail.component';
```
Lets find the location of the template content we removed from `AppComponent` and refer to our new component.
```
<my-hero-detail></my-hero-detail>
```
This would be good enough if the component did not have any inputs. But we do, so we want to pass the selected hero to the `hero` input of the `HeroDetailComponent`, as shown below:
```
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
```
Our `AppComponent`s template should now look like this
+makeExample('toh-3/ts/app/hero-detail.component.pt3.html')
:marked
#### Naming Convention
We want to identify which files are components. Our `AppComponent` is named `app.ts` while our `HeroDetailComponent` is `hero-detail.component.ts`. Thats not very consistent and we can make it easier to know what is in each file by following a naming convention where we identify which files contain a component.
Lets rename `app.ts` to `app.component.ts`.
<!-- TODO
.l-sub-section
:marked
Learn more about naming conventions in the chapter [Naming Conventions]
:marked
-->
Remember that our application kicks off by entering our starting point, which is `app.component.ts`. We just renamed this file, but we also have to change every place we import this module. Our entry point is in our `index.html` file. Lets change the following statement to import `app.component`.
```
System.import('app/app.component');
```
### Checking Our Work
When we view our app in the browser we see the list of heroes. When we select a hero we can see the selected heros details. If we want to show hero details somewhere else in our app we can use the `HeroDetailComponent` and pass in a hero.
Weve created our first reusable component!
<!-- TODO
.l-sub-section
:marked
Learn more about reusable components in the chapter [Reusable Components]
:marked -->
## Creating a Hero Service
Our stakeholders have shared their larger vision for our app. They tell us they want to show the heroes in various ways in different pages. We have a way to select a hero from a list, but we will also need a dashboard with the top heroes and a separate view for editing hero details.
All of these views need hero data. Our `AppComponent` defines and uses a list of heroes, but it is not ideal to create our AppComponent when just the heroes data elsewhere. Fortunately we can create a shared service that will provide the heroes.
### Creating the HeroService
Were going to create a service that can be used by any component that wants hero data. Lets start by creating a file and naming it `hero.service.ts`. We name the class `HeroService` and export it, so our components can import it.
```
export class HeroService { }
```
#### The getHeroes Method
We create a method named `getHeroes` in our `HeroService`. It will return an array of `Hero` objects, so lets import the `Hero` class and define our method.
```
import { Hero } from './hero';
export class HeroService {
getHeroes() : Hero[] {
}
}
```
#### Mocking the Heroes
Our `HeroService` shouldnt be defining the hero data. Instead, the service should handle retrieving the data from another source. That source could be a mock data, a web service, or even local storage. We will design our `HeroService` to get the data from any of these sources and not affect the calling component. This will make it more reusable and more testable.
Once.
We have a list of heroes in `AppComponent`. We will move it to a new file named `mock-heroes.ts` and export the list.
+makeExample('toh-3/ts/app/mock-heroes.ts', 'mocking-heroes')
:marked
### Returning the Mocked Heroes
Our `HeroService` needs to get the list of heroes, so lets import the mocked heroes module. Then well return the HEROES array.
```
import {Hero} from './hero';
import {HEROES} from './mock-heroes';
export class HeroService {
getHeroes() {
return HEROES;
}
}
```
TypeScript can implicitly determine that that return type is `Hero[]` since the return value is of that same type. This allows use to remove the explicit return type from the `getHeroes` method.
### Injecting the Hero Service
Weve set ourselves up so we can use the `HeroService` from other components. Lets import the `HeroService` in our `AppComponent`.
```
import {HeroService} from './hero.service';
```
Importing the service allows us to reference it, but we need to make sure the `HeroService` dependency is instantiated when our component needs it. We inject the `HeroService` into our `AppComponent`s constructor.
```
constructor(private _heroService: HeroService) { }
```
We just injected our dependency into the component, thus we performed Dependency Injection.
.l-sub-section
:marked
Learn more about Dependency Injection in chapter [Dependency Injection](dependency-injection.html)
:marked
We made our instance of the injected `HeroService` be a private property on our `AppComponent` class. As a convention we prefixed the private property with an underscore.
Since we are not defining the heroes in the `AppComponent` any longer, lets refactor the `hero` property declaration to be an uninitialized array of `Hero`.
```
public heroes: Hero[];
```
### The OnInit Lifecycle Hook
When our `AppComponent` is created we want it to get the list of heroes. We need to know when the component is initialized and activated, so well use the `OnInit` lifecycle event to tell us this.
Lets import Angulars `OnInit` interface, implement it on our `AppComponent` and define its required `onInit` method. First we add the `OnInit` interface to the import statement.
```
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES, OnInit} from 'angular2/angular2';
```
Now we implement the interface.
```
class AppComponent implements OnInit {
```
Then we define the `onInit` method and get our heroes from our `HeroService`.
```
onInit() {
this.heroes = this._heroService.getHeroes();
}
```
<!-- TODO
.l-sub-section
:marked
Learn more about lifecycle hooks in chapter [Lifecycle Hooks]
:marked
-->
Why not use the constructor to get the heroes? When we test our application we want an opportunity to create the class without any state being set. This will make it easier to test and reduce external factors, such as calling a service in the constructor. Therefore the constructor is best suited to help us inject dependencies and initialize variables. We need a place to get our heroes right after our class is constructed but before the view is rendered. The OnInit lifecycle hook gives us this opportunity.
<!-- TODO
.l-sub-section
:marked
Learn more about testing components in chapter [Testing Components]
:marked
-->
### Binding the Hero Service
When we view our app in the browser we see we have an error displayed in the developer console
code-example(format="." language="html").
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
:marked
We used Dependency Injection to tell our `AppComponent` that it should inject the `HeroService`. However we need to tell our app about the `HeroService` so it can provide it when needed. The way we do this is by declaring the `HeroService` as a binding when we bootstrap our app.
Lets pass a second argument to the `bootstrap` method to declare the `HeroService` as an application binding.
```
bootstrap(AppComponent, [HeroService]);
```
We can add other bindings here, as needed.
When we view our app in the browser the error is gone and our application runs as expected showing our list of heroes.
## Promises
Our `HeroService` synchronously returns a list of heroes. It operates synchronously because the list of heroes is mocked. What happens when we want to switch that to get the heroes from a web service? The web service call over http would happen asynchronously.
We dont yet call http, but we aspire to in later chapters. So how do we write our `HeroService` so that it wont require refactoring the consumers of `HeroService` later? We make our `HeroService`s `getHeroes` method return a promise to provide the heroes.
The key is that our components wont know how the data is being retrieved. We can return mock heroes or heroes from http, and the component will call the services method the same way.
### Returning a Promise
Lets refactor the `getHeroes` method in `HeroService` to return the heroes in a promise.
```
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
export class HeroService {
getHeroes() {
return Promise.resolve(HEROES);
}
}
```
The `Promise` is immediately resolving and passing the the hero data back in the promise.
### Acting on a Promise
Lets refactor the `getHeroes` method in `HeroService` to return the heroes in a promise. First, we create a new method in the `AppComponent` to get the heroes and named it `getHeroes`.
When we call our heroes we start by resetting the `selectedHero` and `heroes` properties.
```
getHeroes() {
this.selectedHero = undefined;
this.heroes = [];
}
```
The `getHeroes` method in `HeroService` returns a promise. So we cannot simply set the return value to `this.heroes`. The method returns a promise and the `heroes` property expects an array of `Hero`. What do we do?
We define a `then` to handle the response from the promise when it resolves. We will set the heroes inside of the `then`.
```
this._heroService.getHeroes()
.then(heroes => this.heroes = heroes);
```
The `then` accepts a function, in this case a lambda that passes in the heroes and sets them to the `heroes` property on `AppComponent`.
We need to return a value for the heroes from the method so a caller can get the heroes when they are ready. Lets return our components `heroes` property, which we first reset to an empty array.
```
return this.heroes;
```
When we put this all together we see we are setting our heroes to an empty array. Then we call the service and get a promise. Finally we return the reference to our `heroes` property, which has the empty array.
```
getHeroes() {
this.selectedHero = undefined;
this.heroes = [];
this._heroService.getHeroes()
.then(heroes => this.heroes = heroes);
return this.heroes;
}
```
So how do the heroes get populated? When the promise resolves, the `heroes` are updated to include the response from the promise.
Finally we call the method we just created in our `onInit` method.
```
onInit() {
this.heroes = this.getHeroes();
}
```
When we view our app in the browser we can see the heroes are displayed.
We are using mock data right now, but we aspire to call a web service over http asynchronously in the future. When we do refactor to use http, the beauty of the promise we created here is that our component wont have to change at all!
### Reviewing the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
code-example.
angular2-tour-of-heroes
|---- node_modules
|---- app
| |---- app.component.ts
| |---- boot.ts
| |---- hero.ts
| |---- hero-detail.component.ts
| |---- hero.service.ts
| |---- mock-heroes.ts
|---- index.html
|---- tsconfig.json
|---- package.json
:marked
## Recap
### The Road Weve Travelled
Lets take stock in what weve built.
- We created a reusable component
- We learned how to make a component accept input
- We created a service class that can be shared by many components
- We created mock hero data and imported them into our service
- We designed our service to return a promise and our component to get our data from the promise
### The Road Ahead
. . . Well learn more about all of these in the next chapter.
Our Tour of Heroes has become more reusable using shared components and services. We want to create a dashboard, add menu links that route between the views, and format data in a template. As our app evolves, well learn how to design it to make it easier to grow and maintain. Well learn more about these tasks in the coming chapters.

View File

@ -0,0 +1,473 @@
include ../../../../_includes/_util-fns
:marked
# Routing Around the App
Our Tour of Heroes is a single view, but we have new requirements to create other views, such as a dashboard, and navigate between them. Well add a dashboard component and use Angulars router to handle navigation between views. We have another requirement to allow selecting a hero from either the dashboard or the heroes view and route directly to the hero details. Well need to learn about and use route parameters to tackle this.
When were done, users will be able to navigate the app like this:
figure.image-display
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
:marked
Finally, well want to filter and format data in our app using Angulars Pipes.
## Reviewing Where We Left Off
Lets verify that we have the following structure after adding our hero service and hero detail component in the previous chapter:
code-example.
angular2-tour-of-heroes
├── node_modules
├── app
| ├── app.component.ts
| ├── boot.ts
| ├── hero.ts
| ├── hero-detail.component.ts
| ├── hero.service.ts
| └── mock-heroes.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
## Flying Overhead
Before we dash into routing for our Tour of Heroes, lets fly over what were going to need to do. Since well want to be routing to entirely different views, well want to separate a few of the components and their template content. Were also going to be adding two entirely new components. The first for our new requirement, the dashboard. The second component will be to host our menus with routing links and configuration.
Here is our checklist of what well need to tackle
1. Move the heroes from AppComponent to the more aptly named HeroesComponent
1. Create a new AppComponent that will host the menu links and routing
1. Create the DashboardComponent to show our top heroes
1. Create a class to handle routing configuration
Well tackle these by separating the components, creating our new components, and adding routing so we can navigate around our Tour of Heroes.
## Separating the Components
Well want a component to host the menu links for the heroes and the dashboard. This will be the first component that our app loads. But currently our app loads `AppComponent` first, which has our list of heroes. Its time to separate our components so we have a component that hosts our menu links and a component that lists our heroes. Well call these `AppComponent` and `HeroesComponent`.
### Creating the HeroesComponent
Since we have an `AppComponent` that lists heroes, lets start by renaming `app.component.ts` to `hero.component.ts`. Then well rename the component from `AppComponent` to `HeroComponent` and well rename the selector to `my-heroes`.
code-example.
selector: 'my-heroes',
:marked
Finally, well export the `HeroesComponent` as we will want to use it from another module when we define our routing.
code-example.
export class HeroesComponent {
:marked
## Creating the New AppComponent
Our app needs a menu and a place to show the dashboard and heroes views. This is effectively the shell for our app.
Well create a new file named `app.component.ts` and create our new `AppComponent` inside of the file. This will be the first component we load in our app. It will host the menu links, when we create them.
We assign our `AppComponent` a selector of `my-app`.
+makeExample('toh-4/ts/app/app.component.pt4.ts')
:marked
`AppComponent` is the entry point of our app. This makes it the ideal place to bootstrap our app, which is why we pass in the `AppComponent` and the shared `HeroService` that all many of our components will use.
We export our `AppComponent` as well want to refer to it from our bootstrapping process.
## Bootstrapping the Tour of Heroes
The start-up of an app is also known as bootstrapping. We are currently bootstrapping in our `HeroesComponent`, which no longer makes sense. So lets change that and separate this startup logic.
Lets move the bootstrapping logic into a new file. Well create a new file named `boot.ts` in the `app` folder.
Lets add the following lines to `boot.ts`:
+makeExample('toh-4/ts/app/bootstrap.pt4.ts')
:marked
The bootstrap function accepts as its first parameter, the first component that the app will use.
Now lets do a little cleanup work. Lets remove the bootstrap logic and remove `bootstrap` from the import statement in the `heroes.component.ts`.
We had already exported our `AppComponent`, which is why we can now import it and bootstrap it here. We pass in the shared `HeroService` that many of our components will use, so it will be ready when we need it.
Now lets tell our module loader to start by loading our `bootstrap` module. Well do this in our`index.html` file
+makeExample('toh-4/ts/app/index.pt4.html','bootstrap')
:marked
### Viewing our Progress
Lets add a title for our app which well bind to a `title` property on our component. Well set the title to “Tour of Heroes”.
+makeExample('toh-4/ts/app/index.pt4.html','title')
:marked
Our title now belongs in the `AppComponent`, but it also still exists in `HeroesComponent`. So lets tidy up by removing the `title` from the `HeroesComponent` class and template.
When we view our app in the browser we should now only see our title of “Tour of Heroes”.
But where is the rest of our app? We havent shown it yet!
Our apps entry point is the `bootstrap` module which loads the `AppComponent`. `AppComponent` in turn only shows a title.
Our next step is to configure the menu links and routes that will show our views.
## Adding the Router to our App
The Angular router is a separate and distinct module that we can include as needed. Well, our Tour of Heroes app needs routing, so lets add it!
### Including the Router
We add a script tag referencing the router code. Well make sure this comes after the angular script reference.
Then lets set our base href to `/src/` since that is where our source code is located. Our `index.html`s head section should nw look like this:
+makeExample('toh-4/ts/app/index.pt4.html','head')
:marked
Now well be loading the Angular router!
### Configuring Routes
We want to display a menu that has links to a dashboard and to our list of heroes. Lets configure the first route to show our `HeroesComponent`.
Our app will have a few routes and we may want to use them in more than one module. Lets create a file named `route.config.ts` to host our routes.
#### Defining Routes
We want to show our heroes list. So lets define our first route to show our `HeroesComponent` template.
+makeExample('toh-4/ts/app/route.config.pt4.ts','first-route')
:marked
Our route needs a path. This is what will show up in the address bar of the browser. Our path for our `HeroesComponent` will be `/`.
The `component` property identifies the component we will load when we go to this route. In this case we want to load the `HeroesComponent`.
The `as` property is what the route is known as. In other words, it is the name of the route.
We set these three properties in an object and export that object in an array named `APP_ROUTES`. Right now we have one route. But as we add more routes, this technique of hosting them in a `route.config.ts` file and exporting an array of them will make it easier to manage our routes.
#### The RouteConfig Decorator
Now that we have defined a route, we need to tell Angular where to find it. Lets go to our `AppComponent` and add the `RouteConfig` decorator the class. Well need to import the `RouteConfig` decorator from Angulars router module, too.
Now we import the `APP_ROUTES`array of routes that we just created. Well pass these into the `RouteConfig` decorator.
+makeExample('toh-4/ts/app/index.pt4.html','routes-title')
:marked
Our app now has its first route, but we need a place to show the heroes view.
<!-- Learn more about RouteConfig in the chapter [Router] -->
### Showing the View with the Router-Outlet
Angular knows we have a route when we navigate to `/heroes`. But where does the view show in the HTML? We havent told our app how to do that yet!
Lets add the `<router-outlet>` directive to the template of `AppComponent`. The `<router-outlet>` is a placeholder that the router uses to place the views. When we go to the `/heroes` route, the router will show the `HeroesComponent` template where we place the `<router-outlet>`.
+makeExample('toh-4/ts/app/index.pt4.html','router-outlet')
:marked
When need to declare to the `AppComponent` that we are using the `router-outlet` directive. To do this well import a special `ROUTER_DIRECTIVES` array of router specific directives.
```
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
```
Then well declare them to the component in the `@Component` decorators `directives` property.
```
directives: [ROUTER_DIRECTIVES]
```
Lets go view our app in the browser and see where we are. Uh oh, we see an error in the developer console.
code-example(language="html").
EXCEPTION: No provider for Router! (RouterOutlet -> Router)
:marked
Angular is warning us that we are using the router and the `router-outlet` but we did not inject the routers provider. We can fix this by injecting this in the bootstrap module. First well import the `ROUTER_PROVIDERS` from the router module.
```
import {ROUTER_PROVIDERS} from 'angular2/router';
```
Then well pass the `ROUTER_PROVIDERS` to the `bootstrap` method.
```
bootstrap(AppComponent, [ROUTER_PROVIDERS, HeroService]);
```
Now when we view our app in the browser we see our heroes list!
## Creating Navigation Links
Okay, our Tour of Heroes is not quite where we want it yet. Weve created the `AppComponent` which hosts the routing and we move the heroes list to the `HeroesComponent`. But to fulfill our requirements we need to create a dashboard and add a way to navigate between the different views. Lets continue by adding the new dashboard component and the navigation links.
### Empty Dashboard
Routing makes a lot more sense once we have multiple views. We have a heroes view to show but now we need to create our dashboard view. Lets create the `DashboardComponent`so we can finish creating the navigation between the components.
Lets create a super simple dashboard component.
+makeExample('toh-4/ts/app/index.pt4.html','simple-dashboard-component')
:marked
Well come back to the dashboard once we complete routing. For now this will do nicely to help us make sure we can navigate between the `HeroesComponent` and the `DashboardComponent`.
### Configuring the Dashboards Routes
Now that we have a component for our dashboard, lets go configure a route that will take us there.
Well open `route.config.ts` and add another route for the dashboard.
Our dashboard should be the first thing we see when load our app. So the dashboard route will be the default route of `/` while our heroes will now be accessible via the path `/heroes`.
Well also import the `DashboarComponent` so we can route to it with the dashboard route. And well add the dashboard route to the `APP_ROUTES` export.
Our `route.config.ts` should now look like the following code:
+makeExample('toh-4/ts/app/route.config.pt4.ts','dashboard-route')
:marked
Now we two components we can route between and we have defined the routes for both components. Next up, well add navigation links to route between them.
### Navigation Links
Lets add the navigation links to the`AppComponent`s template. The Angular router uses a special `router-link` directive to navigate to the routes we defined. We can think of these as links that will navigate to another component using the router.
We also add a title for our app which well bind to a `title` property on our component.
+makeExample('toh-4/ts/app/index.pt4.html','router-link')
:marked
The `router-link` is not set to url. We bound the `router-link` to the routes that we specified in the `as` property of our `@RouteConfig`. The `as` is the key that we use to reference the routes.
#### Reusable Routing Config
We just hard-coded the routes that the `router-link` properties are bound. We can do better. We recall that we previously created route configuration in `route.config.ts`. We import its `Routes`.
+makeExample('toh-4/ts/app/index.pt4.html','import-app-routes')
:marked
And then we use it to initialize a `routes` property on our `AppComponent`.
+makeExample('toh-4/ts/app/app.component.pt4.ts','initialize-routes-property')
:marked
Then we can simply reference the routes as shown below using our route configuration variables, such as `routes.heroes.as`.
+makeExample('toh-4/ts/app/index.pt4.html','router-link')
:marked
When we view our app in the browser we are brought directly to our dashboard. We can navigate between the dashboard and the heroes til our hearts are content.
## Adding the Top Heroes to Our Dashboard
Our dashboard view is a bit bland as it only contains a title. Lets spice it up by adding the top 4 heroes at a glance.
<!-- Ward sweep section below -->
### Top Heroes Template Content
Lets add the template to our dashboard to show the top four heroes. Well use the `ng-for` directive to iterate over a list of heroes (which we have not retrieved yet) and display them. Well use `<div>` elements as were going to custom style them.
+makeExample('toh-4/ts/app/index.pt4.html','ng-for')
:marked
Weve been down this road before. We are using the `ng-for` directive, so we have to declare it in the component. Lets do that now by first importing `CORE_DIRECTIVES`. (Remember that `CORE_DIRECTIVES` is a convenience array containing the most common directives such as `ng-for`.)
```
import {Component, CORE_DIRECTIVES} from 'angular2/angular2';
```
Then we declare the `CORE_DIRECTIVES` to the component.
```
directives: [CORE_DIRECTIVES]
```
### Using the Shared HeroService
We just iterated over a list of heroes, but we dont have any heroes in the `DashboardComponent`. We do have a `HeroService` that provides heroes. In fact, we already used this service in the `HeroComponent`. Lets re-use this same service for the `DashboardComponent` to get a list of heroes.
Well create a `heroes` property in our `DashboardComponent`.
```
public heroes: Hero[];
```
And we import the `Hero`
```
import {Hero} from './hero';
```
#### Injecting a Service
Well be needing the `HeroService`, so lets import it and inject it into our `DashboardComponent`. Here we import it.
```
import {HeroService} from './hero.service';
```
And here we inject it into our components constructor.
```
constructor(private _heroService: HeroService) { }
```
#### Getting the Heroes on Initialization
We want our heroes to be loaded when the component is initialized, just like we did in the `HeroesComponent`. Well tackle this the same way, by using the onInit Lifecycle hook.
We can implement the `OnInit` interface and code the `onInit` method.
```
export class DashboardComponent implements OnInit {
```
Here we implement the `onInit` method to get our heroes, again just like we did for the `HeroesComponent`.
+makeExample('toh-4/ts/app/app.component.pt4.ts','oninit')
:marked
Notice we did not have to know how to get the heroes, we just needed to know the method to call from the `HeroService`. This is an advantage of using shared services.
When we view our app in the browser we see the dashboard light up with all of our heroes. This isnt exactly what we want so lets trim that down to the top four heroes.
### Slicing with Pipes
Our requirement is to show the top four heroes. If only we had something that would automatically get a subset of the data. Well, we do! They are called Pipes.
Angular has various built-in pipes that make formatting and filtering data easy. Well take advantage of a pipe named `slice` to get a slice of our heroes array.
```
<div *ng-for="#hero of heroes | slice:0:4">
```
After the `ng-for` we added a pipe character and then the `slice` pipe. We tell the `slice` pipe to start at the 0th item in the array and get four items.
.l-sub-section
:marked
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
:marked
When we view our app we now see the first four heroes are displayed.
### Heroes with Style
Our creative designers have added a requirement that the dashboard should show the heroes in a row of rectangles. Weve written some CSS to achieve this along with some simple media queries to achieve responsive design.
We could put the CSS in the component, but there are over 30 lines of CSS and it would get crowded fast. Most editors make it easier to code CSS in a *.css file too. Fortunately, we can separate the styles into their own file and reference them.
#### Adding the Dashboards CSS File
Lets create a file to hold the `DashboardComponent`s CSS. Well name it `dashboard.component.css` and put it in the `app` folder.
Now lets add the following CSS to the file.
+makeExample('toh-4/ts/app/index.pt4.html','css')
:marked
We need to reference the file from our component so the styles will be applied properly. Lets add this reference to the components `styleUrls` property.
```
styleUrls: ['app/dashboard.component.css'],
```
The `styleUrls` property is an array, which we might guess suggests that we can add multiple styles from different locations. And we would be right! In this case we have one file, but we could add more for our component if needed.
#### Template Urls
While we are at it, lets move our HTML for the `DashboardComponent` to its own file. Well create a file named `dashboard.component.html` in the `app` folder and move the HTML there.
We need to reference the the template, so lets change our components `template` property to `templateUrl` and set it to the location of the file.
```
template: 'app/dashboard.component.html',
```
Notice we are now using single quotes and not the back-ticks since we only have the need for a single line string.
#### Applying the Styles to Our Template
Now that we have some style, lets take advantage of it by applying it to our template. Our template should now look like this:
+makeExample('toh-4/ts/app/index.pt4.html','template-styled')
:marked
When we view our app in the browser it now shows our dashboard with our four top heroes in a row.
## Styling the Navigation Links
Our creative design team requested that the navigation links also be styled. Lets make the navigation links look more like selectable buttons.
### Defining Styles for Navigation Links
Lets add the following styles to our `AppComponent`s `styles` property. Well define some classes named `router-link` that style the default, active, visited and hover selectors. The active selector changes the color of the link to make it easy to identify which link is selected.
+makeExample('toh-4/ts/app/app.component.pt4.ts','styles')
:marked
This time we define the styles in our component because there are only a few of them.
### Applying Styles to the Navigation Links
Now that we have styles for our navigation links, lets apply them in the `AppComponent` template. When we are done, our component will look like the following:
+makeExample('toh-4/ts/app/index.pt4.html','styled-nav-links')
:marked
#### Where Did the Active Route Come From?
The Angular Router makes it easy for us to style our active navigation link. The `router-link-active` class is automatically added to the Routers active route for us. So all we have to do is define the style for it.
Sweet! When we view our app in the browser we see the navigation links are now styled, as are our top heroes!
figure.image-display
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations")
:marked
## Add Route to Hero Details
We can navigate between the dashboard and the heroes view, but we have a requirement from our users to be able to select a hero from either of those views and go directly to the selected heros details. Lets configure a route to go directly to the `HeroDetailComponent` passing the heros id as a parameter.
### Configuring a Route with a Parameter
Weve already added a few routes to the `routes.config.ts` file, so its natural that wed start there to add the route to go to the `HeroDetailComponent`. Lets start by adding the import statement for the component.
+makeExample('toh-4/ts/app/route.config.pt4.ts','route-parameter-import')
:marked
Now we add a route for the details to the `Routes` object.
+makeExample('toh-4/ts/app/route.config.pt4.ts','route-parameter-detail')
:marked
The route will lead to the `HeroDetailComponent`, passing along the value for the heros id. The routing configuration identifies parameters as parts of the path that are prefixed with a `:` such as `:id`.
### Receiving a Parameter
We want to navigate to the `HeroDetailComponent`, so lets modify it to accept the `id` parameter. We import the `RouteParams` so we can access the parameter.
We assign our `AppComponent` a selector of `my-app`.
+makeExample('toh-4/ts/app/app.component.pt4.ts','import-params')
:marked
Now we inject the `RouteParams` into the `HeroDetailComponent` constructor.
+makeExample('toh-4/ts/app/app.component.pt4.ts','inject-routeparams')
:marked
We want to immediately access the parameter, so lets implement the `OnInit` interface and its `onInit` method.
+makeExample('toh-4/ts/app/app.component.pt4.ts','access-params')
:marked
And lets not forget to import `OnInit`.
+makeExample('toh-4/ts/app/app.component.pt4.ts','import-onit')
:marked
Using the `onInit`method, we can grab the parameter as soon as the component initializes. Well access the parameter by name and later well get the hero by its `id`.
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-id-param')
:marked
Our `HeroDetailComponent` is already used in the `HeroesComponent`. When we select a hero from the list we are passing the hero object from the list to the `HeroDetailComponent`. We want this component to support that functionality or be able to accept the heros id. Lets revise the logic in the `onInit` to handle this.
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-hero-id')
:marked
Our component will first check if it has a hero. If it doesnt it will then check for the routing parameter so it can get the hero.
.l-sub-section
:marked
Learn more about RouteParams in the chapter [Router](../guide/router.html)
:marked
Getting the Hero
When we pass the id to the `HeroDetailComponent` we need to go get the hero from our `HeroService`. Lets import the `HeroService` so we can use it to get our hero.
+makeExample('toh-4/ts/app/bootstrap.pt4.ts','import-hero-service')
:marked
And then we inject the `HeroService` into the constructor.
+makeExample('toh-4/ts/app/app.component.pt4.ts','inject-hero-service')
:marked
We then stub out the call to the `HeroService` to get the hero by the heros id. But wait a second, we dont have a way to get the hero by id … yet.
Our `HeroService` is the right place to get a single hero. Well create a method named `getHero` that will accept a parameter, find the hero, and return the hero in a promise.
We add this method to the `HeroService`.
+makeExample('toh-4/ts/app/hero.service.pt4.ts','get-hero-method')
:marked
Then we go back to our `HeroDetailComponent` and we can call the `getHero` method.
+makeExample('toh-4/ts/app/app.component.pt4.ts','onit-hero-method')
:marked
We grab the hero and set it to the local `hero` property. Now we have everything in place to receive the parameter.
### Select a Hero on the Dashboard
When a user selects a hero in the dashboard, we want to route to the details. Lets open our dashboards template and add a click event to each hero in the template.
+makeExample('toh-4/ts/app/index.pt4.html','select-hero-click-event')
:marked
The click event will call the `gotoDetail` method in the `DashboardComponent`. We dont have that method yet, so lets create it. Well want to use the router to navigate to the details route we created. So we have to import the router, and while we are at it, well import the `Routes` object we created that describe our routes.
+makeExample('toh-4/ts/app/bootstrap.pt4.ts','import-router')
:marked
Now we can write our method to navigate to the route and pass the parameter. Well use the routers `navigate` method and pass an array that has 2 parameters. The first is the name of the route (the `as` property in the `RouteConfig`). The second is an object with the parameters and values.
+makeExample('toh-4/ts/app/route.config.pt4.ts','router-navigate-method')
:marked
Now when we view our app in the browser and select a hero from the dashboard, we go directly to the heros details!
.l-sub-section
:marked
Learn more about RouteParams in the chapter [Router](../guide/router.html)
:marked
### Select a Hero on the HeroesComponent
When a user selects a hero in the dashboard, we go to the details. But we also want this to happen from the `HeroesComponent`. Lets add the same changes to the `HeroesComponent` that we made to the dashboard.
+makeExample('toh-4/ts/app/app.component.pt4.ts','select-hero')
:marked
The requirement here is to show the hero when selected and allow the user to the details via a button. So when a user selects a hero we want to show the heros name and provide a button to navigate to the details.
Lets open the `HeroesComponent`, remove the `my-hero-detail` component, and change the template to display the heros name instead.
+makeExample('toh-4/ts/app/index.pt4.html','display-hero-name')
:marked
We also added a button with a click event that will call our `gotoDetail` method in our `HeroesComponent`.
Notice we also used the `uppercase` pipe to format the selected heros name. Pipes are extremely helpful at formatting and filtering.
.l-sub-section
:marked
Learn more about Pipes in the chapter [Pipes](../guide/pipes.html)
:marked
When we view the app in our browser we can now navigate from the dashboard or the heroes component directly to the selected heros details!
### Cleaning Up Templates and Styles
Weve added a lot of HTML and CSS in our template and styles, respectively, in the `HeroesComponent`. Lets move each of these to their own files.
We move the HTML for the `HeroesComponent` template to `heroes.component.html`. Then we reference the file in the components `templateUrl` property.
Now our `HeroesComponent` looks much cleaner and easier to maintain since our template and styles are in another file.
+makeExample('toh-4/ts/app/app.component.pt4.ts','reference-heroes-component')
:marked
Well also move the HTML out of the `HeroDetailComponent` and into its own file named `hero-detail.component.html`. Then we reference the file from the `templateUrl` property.
+makeExample('toh-4/ts/app/app.component.pt4.ts','reference-hero-detail-component')
:marked
### Adding Styles to the App
When we add styles to a component we are making it easier to package everything a component needs together. The HTML, the CSS, and the code are all together in one convenient place. However we can also add styles at an app level outside of a component.
Our designers just gave us a few more basic styles to apply to our entire app. Lets add some CSS in a file `styles.css` to the `src` folder to style the apps basic elements.
+makeExample('toh-4/ts/app/index.pt4.html','basic-styles')
:marked
And lets reference the stylesheet from the `index.html`.
+makeExample('toh-4/ts/app/index.pt4.html','stylesheet')
:marked
When we view our app in the browser we can see everything still works as expected!
### Reviewing the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
code-example.
angular2-tour-of-heroes
|---- node_modules
|---- app
| |---- app.component.ts
| |---- boot.ts
| |---- dashboard.component.css
| |---- dashboard.component.html
| |---- dashboard.component.ts
| |---- hero.ts
| |---- hero-detail.component.html
| |---- hero-detail.component.ts
| |---- hero.service.ts
| |---- heroes.component.css
| |---- heroes.component.html
| |---- heroes.component.ts
| |---- mock-heroes.ts
| |---- route.config.ts
|---- index.html
|---- styles.css
|---- tsconfig.json
|---- package.json
.l-main-section
:marked
## Recap
### The Road Weve Travelled
Lets take stock in what weve built.
- We added the router to navigate between different components and their templates
- We learned how to create router links to represent navigation menu items
- We extended a component to either accept a hero as input or accept a router parameter to get the hero
- We extended our shared service by adding a new method to it
- We added the `slice` pipe to filter the top heroes, and the `uppercase` pipe to format data
### The Road Ahead
Our Tour of Heroes has grown to reuse services, share components, route between components and their templates, and filter and format data with pipes. We have many of the foundations to build an application. In the next chapter well explore how to replace our mock data with real data using http.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB