[WIP] docs(toh): copyedits to conform to Google doc standards (#3032) (#3077)

This commit is contained in:
Ward Bell 2017-03-21 11:08:09 -07:00 committed by GitHub
parent be53a50b6b
commit 0fc40284dd
33 changed files with 1326 additions and 1364 deletions

View File

@ -1,39 +0,0 @@
// #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>
<input value="{{hero.name}}" placeholder="name">
</div>
`
// #enddocregion editing-Hero
// #docregion app-component-1
export class AppComponent {
title = 'Tour of Heroes';
hero = 'Windstorm';
}
// #enddocregion app-component-1

View File

@ -0,0 +1,44 @@
import { Component } from '@angular/core';
let t = {
// #docregion show-hero
template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`
// #enddocregion show-hero
};
t = {
// #docregion show-hero-2
template: `<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>`
// #enddocregion show-hero-2
};
t = {
// #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 name-input
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name">
</div>
// #enddocregion name-input
*/
/////////////////
@Component(t)
// #docregion app-component-1
export class AppComponent {
title = 'Tour of Heroes';
hero = 'Windstorm';
}
// #enddocregion app-component-1

View File

@ -3,7 +3,8 @@
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
"!**/*.js",
"!**/*.[1].*"
],
"tags": ["tutorial", "tour", "heroes"]
}

View File

@ -1,4 +1,4 @@
// #docregion pt1
// #docregion
import { Component } from '@angular/core';
// #docregion hero-class-1
@ -10,6 +10,7 @@ export class Hero {
@Component({
selector: 'my-app',
// #docregion editing-Hero
template: `
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
@ -19,6 +20,7 @@ export class Hero {
<input [(ngModel)]="hero.name" placeholder="name">
</div>
`
// #enddocregion editing-Hero
})
export class AppComponent {
title = 'Tour of Heroes';
@ -29,4 +31,3 @@ export class AppComponent {
};
// #enddocregion hero-property-1
}
// #enddocregion pt1

View File

@ -1,14 +1,14 @@
// #docregion
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
FormsModule // <-- import the FormsModule before binding with [(ngModel)]
],
declarations: [
AppComponent

View File

@ -128,6 +128,6 @@ function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
};
}

View File

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

View File

@ -3,7 +3,8 @@
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
"!**/*.js",
"!**/*.[1].*"
],
"tags": ["tutorial", "tour", "heroes"]
}

View File

@ -128,6 +128,6 @@ function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
};
}

View File

@ -0,0 +1,12 @@
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<!-- #docregion hero-detail-binding -->
<hero-detail [hero]="selectedHero"></hero-detail>
<!-- #enddocregion hero-detail-binding -->

View File

@ -0,0 +1,35 @@
// #docplaster
// #docregion v1
import { Component } from '@angular/core';
// #enddocregion v1
// #docregion hero-import
import { Hero } from './hero';
// #enddocregion hero-import
// #docregion v1
@Component({
selector: 'hero-detail',
// #enddocregion v1
// #docregion template
template: `
<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>
</div>
`
// #enddocregion template
// #docregion v1
})
export class HeroDetailComponent {
// #enddocregion v1
// #docregion hero
hero: Hero;
// #enddocregion hero
// #docregion v1
}
// #enddocregion v1

View File

@ -3,7 +3,8 @@
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
"!**/*.js",
"!**/*.[1].*"
],
"tags": ["tutorial", "tour", "heroes"]
}

View File

@ -31,7 +31,7 @@ const HEROES: Hero[] = [
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
// #enddocregion hero-detail-template
styles: [`

View File

@ -3,22 +3,22 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppComponent } from './app.component';
// #docregion hero-detail-import
import { HeroDetailComponent } from './hero-detail.component';
// #enddocregion hero-detail-import
// #docregion declarations
@NgModule({
imports: [
BrowserModule,
FormsModule
],
// #docregion declarations
declarations: [
AppComponent,
HeroDetailComponent
],
// #enddocregion declarations
bootstrap: [ AppComponent ]
})
export class AppModule { }
// #enddocregion declarations

View File

@ -1,18 +1,12 @@
// #docplaster
// #docregion
// #docregion v1
// #docregion import-input
import { Component, Input } from '@angular/core';
// #enddocregion import-input
// #enddocregion v1
// #docregion hero-import
import { Hero } from './hero';
// #enddocregion hero-import
// #docregion v1
// #docregion template
@Component({
selector: 'my-hero-detail',
// #enddocregion v1
// #docregion template
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
@ -23,17 +17,13 @@ import { Hero } from './hero';
</div>
</div>
`
// #enddocregion template
// #docregion v1
})
// #enddocregion template
// #docregion class
export class HeroDetailComponent {
// #enddocregion v1
// #docregion hero-input
@Input()
// #docregion hero
hero: Hero;
@Input() hero: Hero;
// #enddocregion hero
// #enddocregion hero-input
// #docregion v1
}
// #enddocregion v1
// #docregion class

View File

@ -128,6 +128,6 @@ function getPageElts() {
return {
heroes: element.all(by.css('my-app li')),
selected: element(by.css('my-app li.selected')),
heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div'))
heroDetail: element(by.css('my-app > div, my-app > hero-detail > div'))
};
}

View File

@ -17,7 +17,7 @@ import { HeroService } from './hero.service.2';
<div *ngFor="let hero of heroes" (click)="onSelect(hero)">
{{hero.name}}
</div>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
// #docregion providers
providers: [HeroService]

View File

@ -20,7 +20,7 @@ import { HeroService } from './hero.service';
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
// #enddocregion template
styles: [`

View File

@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'my-hero-detail',
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>

View File

@ -57,7 +57,7 @@ describe('Tutorial part 5', () => {
selectedHero: element(by.css('my-app li.selected')),
selectedHeroSubview: element(by.css('my-app my-heroes > div')),
heroDetail: element(by.css('my-app my-hero-detail > div'))
heroDetail: element(by.css('my-app hero-detail > div'))
};
}

View File

@ -10,7 +10,7 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
// #docregion metadata
@Component({
selector: 'my-hero-detail',
selector: 'hero-detail',
templateUrl: './hero-detail.component.html',
// #enddocregion metadata, v2
styleUrls: [ './hero-detail.component.css' ]

View File

@ -63,7 +63,7 @@ describe('Tutorial part 6', () => {
selectedHero: element(by.css('my-app li.selected')),
selectedHeroSubview: element(by.css('my-app my-heroes > div:last-child')),
heroDetail: element(by.css('my-app my-hero-detail > div')),
heroDetail: element(by.css('my-app hero-detail > div')),
searchBox: element(by.css('#search-box')),
searchResults: element.all(by.css('.search-result'))

View File

@ -8,7 +8,7 @@ import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
selector: 'my-hero-detail',
selector: 'hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: [ './hero-detail.component.css' ]
})

View File

@ -865,7 +865,7 @@ a#heroes-functionality
- Delete the `selector` (routed components don't need them).
- Delete the `<h1>`.
- Relabel the `<h2>` to `<h2>HEROES</h2>`.
- Delete the `<my-hero-detail>` at the bottom of the template.
- Delete the `<hero-detail>` at the bottom of the template.
- Rename the `AppComponent` class to `HeroListComponent`.
- Copy the `hero-detail.component.ts` and the `hero.service.ts` files into the `heroes` subfolder.
- Create a (pre-routing) `heroes.module.ts` in the heroes folder that looks like this:

View File

@ -68,6 +68,8 @@ a(id='toc')
to all components, services, and other symbols.
This helps make the app cleaner, easier to read and maintain, and more testable.
a#rule-of-one
:marked
### <a id="01-01"></a>_Rule of One_
#### <a href="#01-01">Style 01-01</a>
.s-rule.do

View File

@ -7,33 +7,33 @@
},
"toh-pt1": {
"title": "The Hero Editor",
"intro": "We build a simple hero editor.",
"intro": "Build a simple hero editor.",
"nextable": true
},
"toh-pt2": {
"title": "Master/Detail",
"intro": "We build a master/detail page with a list of heroes.",
"intro": "Build a master/detail page with a list of heroes.",
"nextable": true
},
"toh-pt3": {
"title": "Multiple Components",
"intro": "We refactor the master/detail view into separate components.",
"intro": "Refactor the master/detail view into separate components.",
"nextable": true
},
"toh-pt4": {
"title": "Services",
"intro": "We create a reusable service to manage our hero data calls.",
"intro": "Create a reusable service to manage the hero data calls.",
"nextable": true
},
"toh-pt5": {
"title": "Routing",
"intro": "We add the Angular Router and learn to navigate among the views.",
"intro": "Add the Angular component router and learn to navigate among the views.",
"nextable": true
},
"toh-pt6": {
"title": "HTTP",
"subtitle": "Getting and saving data",
"intro": "We convert our service and components to use Angular's HTTP service.",
"intro": "Convert the service and components to use Angular's HTTP service.",
"nextable": true
}
}

View File

@ -2,80 +2,82 @@ block includes
include ../_util-fns
:marked
Our grand plan for this tutorial is to build an app to help a staffing agency manage its stable of heroes.
Even heroes need to find work.
The grand plan for this tutorial is to build an app that helps a staffing agency manage its stable of heroes.
Of course we'll only make a little progress in this tutorial. What we do build will
have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying
The Tour of Heroes app covers the core fundamentals of Angular. You'll build a basic app that
has many of the features you'd expect to find in a full-blown, data-driven app: acquiring and displaying
a list of heroes, editing a selected hero's detail, and navigating among different
views of heroic data.
The Tour of Heroes covers the core fundamentals of Angular.
Well use built-in directives to show/hide elements and display lists of hero data.
Well create a component to display hero details and another to show an array of heroes.
We'll use one-way data binding for read-only data. We'll add editable fields to update a model
with two-way data binding. We'll bind component methods to user events like key strokes and clicks.
Well learn to select a hero from a master list and edit that hero in the details view. We'll
format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components.
You'll use built-in directives to show and hide elements and display lists of hero data.
You'll create components to display hero details and show an array of heroes.
You'll use one-way data binding for read-only data. You'll add editable fields to update a model
with two-way data binding. You'll bind component methods to user events, like keystrokes and clicks.
You'll enable users to select a hero from a master list and edit that hero in the details view. You'll
format data with pipes. You'll create a shared service to assemble the heroes.
And you'll use routing to navigate among different views and their components.
<!-- CF: Should this be a bullet list? -->
Well learn enough core Angular to get started and gain confidence that
Angular can do whatever we need it to do.
We'll be covering a lot of ground at an introductory level but well find plenty of links
to chapters with greater depth.
You'll learn enough core Angular to get started and gain confidence that
Angular can do whatever you need it to do.
You'll cover a lot of ground at an introductory level, and you'll find many links
to pages with greater depth.
When you're done with this tutorial, the app will look like this <live-example name="toh-6"></live-example>.
Run the <live-example name="toh-6"></live-example>.
.l-main-section
:marked
## The End Game
## The end game
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
view and our most heroic heroes:
Here's a visual idea of where this tutorial leads, beginning with the "Dashboard"
view and the most heroic heroes:
figure.image-display
img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard")
:marked
Above the dashboard are two links ("Dashboard" and "Heroes").
We could click them to navigate between this Dashboard and a Heroes view.
You can click the two links above the dashboard ("Dashboard" and "Heroes")
to navigate between this Dashboard view and a Heroes view.
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
of that hero where we can change the hero's name.
If you click the dashboard hero "Magneta," the router opens a "Hero Details" view
where you can change the hero's name.
figure.image-display
img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app")
:marked
Clicking the "Back" button would return us to the "Dashboard".
Links at the top can take us to either of the main views.
We'll click "Heroes". The app takes to the "Heroes" master list view.
Clicking the "Back" button returns you to the Dashboard.
Links at the top take you to either of the main views.
If you click "Heroes," the app displays the "Heroes" master list view.
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app")
// CF: The ability to add heroes isn't shown in the images or discussed in this page. Should that be added?
:marked
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
When you click a different hero name, the read-only mini detail beneath the list reflects the new choice.
We click the "View Details" button to drill into the
editable details of our selected hero.
You can click the "View Details" button to drill into the
editable details of the selected hero.
The following diagram captures all of our navigation options.
The following diagram captures all of the navigation options.
figure.image-display
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
:marked
Here's our app in action
Here's the app in action:
figure.image-display
img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action")
.l-main-section
:marked
## Up Next
## Up next
Well build this Tour of Heroes together, step by step.
We'll motivate each step with a requirement that we've
met in countless applications. Everything has a reason.
You'll build the Tour of Heroes app, step by step.
Each step is motivated with a requirement that you've likely
met in many applications. Everything has a reason.
And well meet many of the core fundamentals of Angular along the way.
Along the way, you'll become familiar with many of the core fundamentals of Angular.

View File

@ -2,11 +2,10 @@ include ../_util-fns
:marked
## Setup to develop locally
Real application development takes place in a local development environment like your machine.
Follow the [setup](../guide/setup.html) instructions for creating a new project
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>
after which the file structure should look like this:
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>.
The file structure should look like this:
.filetree
.file angular-tour-of-heroes
@ -22,172 +21,159 @@ include ../_util-fns
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file node_modules ...
.file package.json
:marked
When we're done with this first episode, the app runs like this <live-example></live-example>.
When you're done with this page, the app should look like this <live-example></live-example>.
a#keep-transpiling
:marked
## Keep the app transpiling and running
We want to start the TypeScript compiler, have it watch for changes, and start our server.
Do this by entering the following command in the terminal window.
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell").
npm start
:marked
This command runs the compiler in watch mode, starts the server, launches the app in a browser,
and keeps the app running while we continue to build the Tour of Heroes.
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
.l-main-section
:marked
## Show our Hero
We want to display Hero data in our app
## Show the hero
Add two properties to the `AppComponent`: a `title` property for the app name and a `hero` property
for a hero named "Windstorm."
Update the `AppComponent` so it has two properties: &nbsp; a `title` property for the application name and a `hero` property
for a hero named "Windstorm".
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'app-component-1', 'src/app/app.component.ts (AppComponent class)')(format=".")
+makeExample('toh-1/ts/app/app.component.1.ts', 'app-component-1', 'app.component.ts (AppComponent class)')(format=".")
:marked
Now update the template in the `@Component` decoration with data bindings to these new properties.
Now update the template in the `@Component` decorator with data bindings to these new properties.
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero')
+makeExample('toh-1/ts/app/app.component.1.ts', 'show-hero', 'app.component.ts (@Component)')(format='.')
:marked
The browser should refresh and display our title and hero.
The browser refreshes and displays the title and hero name.
The double curly braces are Angular's *interpolation binding* syntax.
These interpolation bindings present the component's `title` and `hero` property values,
as strings, inside the HTML header tags.
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
This is the "interpolation" form of one-way data binding.
.l-sub-section
:marked
Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html).
Read more about interpolation in the [Displaying Data](../guide/displaying-data.html) page.
:marked
### Hero object
At the moment, our hero is just a name. Our hero needs more properties.
Let's convert the `hero` from a literal string to a class.
The hero needs more properties.
Convert the `hero` from a literal string to a class.
Create a `Hero` class with `id` and `name` properties.
For now put this near the top of the `app.component.ts` file, just below the import statement.
Add these properties near the top of the `app.component.ts` file, just below the import statement.
+makeExample('toh-1/ts/src/app/app.component.ts', 'hero-class-1', 'src/app/app.component.ts (Hero class)')(format=".")
:marked
Now that we have a `Hero` class, lets refactor our components `hero` property to be of type `Hero`.
Then initialize it with an id of `1` and the name, "Windstorm".
In the `Hero` class, refactor the component's `hero` property to be of type `Hero`,
then initialize it with an `id` of `1` and the name `Windstorm`.
+makeExample('toh-1/ts/src/app/app.component.ts', 'hero-property-1', 'src/app/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.
Because you changed the hero from a string to an object,
update the binding in the template to refer to the hero's `name` property.
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'show-hero-2')
+makeExample('toh-1/ts/app/app.component.1.ts', 'show-hero-2')
:marked
The browser refreshes and continues to display our heros name.
The browser refreshes and continues to display the hero's name.
### Adding more HTML
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`.
### Adding HTML with multi-line template strings
+makeExample('toh-1/ts-snippets/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.
To show all of the hero's properties,
add a `<div>` for the hero's `id` property and another `<div>` for the hero's `name`.
To keep the template readable, place each `<div>` on its own line.
### Multi-line template strings
The backticks around the component template let you put the `<h1>`, `<h2>`, and `<div>` elements on their own lines,
thanks to the <i>template literals</i> feature in ES2015 and TypeScript. For more information, see
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" target="_blank" title="template literal">Template literals</a>.
We could make a more readable template with string concatenation
but that gets ugly fast, it is harder to read, and
it is easy to make a spelling error. Instead,
lets take advantage of the template strings feature
in ES2015 and TypeScript to maintain our sanity.
Change the quotes around the template to back-ticks and
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'multi-line-strings', 'src/app/app.component.ts (AppComponent\'s template)')
.callout.is-important
header A back-tick is not a single quote
:marked
**Be careful!** A back-tick (`) looks a lot like a single quote (').
It's actually a completely different character.
Back-ticks can do more than demarcate a string.
Here we use them in a limited way to spread the template over multiple lines.
Everything between the back-ticks at the beginning and end of the template
is part of a single template string.
+makeExample('toh-1/ts/app/app.component.1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)')(format='.')
.l-main-section
:marked
## Editing Our Hero
## Edit the hero name
We want to be able to edit the hero name in a textbox.
Users should be able to edit the hero name in an `<input>` textbox.
The textbox should both _display_ the hero's `name` property
and _update_ that property as the user types.
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
You need a two-way binding between the `<input>` form element and the `hero.name` property.
### Two-way binding
Refactor the hero name in the template so it looks like this:
+makeExample('toh-1/ts/app/app.component.1.ts', 'name-input')(format='.')
+makeExample('toh-1/ts-snippets/app.component.snippets.pt1.ts', 'editing-Hero', 'src/appapp.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.
When we change the name, we notice that our change
is not reflected in the `<h2>`. We won't get the desired behavior
with a one-way binding to `<input>`.
`[(ngModel)]` is the Angular syntax to bind the `hero.name` property
to the textbox.
Data flow _in both directions_: from the property to the textbox;
and from the textbox back to the property.
### Two-Way Binding
Unfortunately, immediately after this change, the application breaks.
If you looked in the browser console, you'd see Angular complaining that
"`ngModel` ... isn't a known property of `input`."
We intend to display the name of the hero in the `<input>`, change it,
and see those changes wherever we bind to the heros name.
In short, we want two-way data binding.
Although `NgModel` is a valid Angular directive, it isn't available by default.
It belongs to the optional `FormsModule`.
You must opt-in to using that module.
Before we can use two-way data binding for **form inputs**, we need to import the `FormsModule`
package in our Angular module. We add it to the `NgModule` decorator's `imports` array. This array contains the list
of external modules used by our application.
Now we have included the forms package which includes `ngModel`.
### Import the _FormsModule_
+makeExample('toh-1/ts/src/app/app.module.ts', '', 'src/app/app.module.ts (FormsModule import)')
Open the `app.module.ts` file and import the `FormsModule` symbol from the `@angular/forms` library.
Then add the `FormsModule` to the `@NgModule` metadata's `imports` array, which contains the list
of external modules that the app uses.
The updated `AppModule` looks like this:
+makeExample('toh-1/ts/src/app/app.module.ts', '', 'app.module.ts (FormsModule import)')
.l-sub-section
:marked
Learn more about the `FormsModule` and `ngModel` in the
[Forms](../guide/forms.html#ngModel) and
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
Read more about `FormsModule` and `ngModel` in the
[Two-way data binding with ngModel](../guide/forms.html#ngModel) section of the
[Forms](../guide/forms.html) guide and the
[Two-way binding with NgModel](../guide/template-syntax.html#ngModel) section of the
[Template Syntax](../guide/template-syntax.html) guide.
:marked
Lets update the template to use the **`ngModel`** built-in directive for two-way binding.
Replace the `<input>` with the following HTML
code-example(language="html").
&lt;input [(ngModel)]="hero.name" placeholder="name">
:marked
The browser refreshes. We see our hero again. We can edit the heros name and
see the changes reflected immediately in the `<h2>`.
When the browser refreshes, the app should work again.
You can edit the hero's name and see the changes reflected immediately in the `<h2>` above the textbox.
.l-main-section
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
## The road you've travelled
Take stock of what you've built.
* Our Tour of Heroes uses the double curly braces of interpolation (a kind of one-way data binding)
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 `ngModel` directive.
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
* The Tour of Heroes app uses the double curly braces of interpolation (a type of one-way data binding)
to display the app title and properties of a `Hero` object.
* You wrote a multi-line template using ES2015's template literals to make the template readable.
* You added a two-way data binding to the `<input>` element
using the built-in `ngModel` directive. This binding both displays the hero's name and allows users to change it.
* The `ngModel` directive propagates changes to every other binding of the `hero.name`.
Run the <live-example></live-example> for this part.
Your app should look like this <live-example></live-example>.
Here's the complete `app.component.ts` as it stands now:
+makeExample('toh-1/ts/src/app/app.component.ts', 'pt1', 'src/app/app.component.ts')
+makeExample('toh-1/ts/src/app/app.component.ts', '', 'src/app/app.component.ts')
.l-main-section
:marked
## The Road Ahead
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
We also want to allow the user to select a hero and display their details.
Well learn more about how to retrieve lists, bind them to the
template, and allow a user to select a hero in the
[next tutorial chapter](./toh-pt2.html).
## The road ahead
In the [next tutorial page](./toh-pt2.html), you'll build on the Tour of Heroes app to display a list of heroes.
You'll also allow the user to select heroes and display their details.
You'll learn more about how to retrieve lists and bind them to the template.

View File

@ -1,22 +1,17 @@
include ../_util-fns
:marked
Our story needs more heroes.
Well expand our Tour of Heroes app to display a list of heroes,
allow the user to select a hero, and display the heros details.
In this page, you'll expand the Tour of Heroes app to display a list of heroes, and
allow users to select a hero and display the hero's details.
Run the <live-example></live-example> for this part.
Lets take stock of what well need to display a list of heroes.
First, we need a list of heroes. We want to display those heroes in the views template,
so well need a way to do that.
When you're done with this page, the app should look like this <live-example></live-example>.
.l-main-section
:marked
## Where We Left Off
Before we continue with Part 2 of the Tour of Heroes,
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.
## Where you left off
Before you continue with this page of the Tour of Heroes,
verify that you have the following structure after [The Hero Editor](./toh-pt1.html) page.
If your structure doesn't match, go back to that page to figure out what you missed.
.filetree
.file angular-tour-of-heroes
@ -32,278 +27,264 @@ include ../_util-fns
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file node_modules ...
.file 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
## Keep the app transpiling and running
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell").
npm start
:marked
This will keep the application running while we continue to build the Tour of Heroes.
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
.l-main-section
:marked
## Displaying Our Heroes
### Creating heroes
Lets create an array of ten heroes.
## Displaying heroes
To display a list of heroes, you'll add heroes to the view's template.
### Create heroes
Create an array of ten heroes.
+makeExample('toh-2/ts/src/app/app.component.ts', 'hero-array', 'src/app/app.component.ts (hero array)')
:marked
The `HEROES` array is of type `Hero`, the class defined in part one,
to create an array of heroes.
We aspire to fetch this list of heroes from a web service, but lets take small steps
first and display mock heroes.
The `HEROES` array is of type `Hero`, the class defined in the previous page.
Eventually this app will fetch the list of heroes from a web service, but for now
you can display mock heroes.
### Exposing heroes
Lets create a public property in `AppComponent` that exposes the heroes for binding.
### Expose heroes
Create a public property in `AppComponent` that exposes the heroes for binding.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'hero-array-1', 'src/app/app.component.ts (hero array property)')
+makeExample('toh-2/ts/app/app.component.1.html', '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.
The `heroes` type isn't defined because TypeScript infers it from the `HEROES` array.
.l-sub-section
:marked
We could have defined the heroes list here in this component class.
But we know that ultimately well get the heroes from a data service.
Because we know where we are heading, it makes sense to separate the hero data
from the class implementation from the start.
:marked
### 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.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-template-1', 'src/app/app.component.ts (heroes template)')
The hero data is separated from the class implementation
because ultimately the hero names will come from a data service.
:marked
Now we have a template that we can fill with our heroes.
### Display hero names in a template
To display the hero names in an unordered list,
insert the following chunk of HTML below the title and above the hero details.
### Listing heroes with ngFor
We want to bind the array of `heroes` in our component to our template, iterate over them,
+makeExample('toh-2/ts/app/app.component.1.html', 'heroes-template-1', 'app.component.ts (heroes template)')(format='.')
:marked
Now you can fill the template with hero names.
### List heroes with ngFor
The goal is to bind the array of `heroes` in the component to the 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 `*ngFor`.
Modify the `<li>` tag by adding the built-in directive `*ngFor`.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-ngfor-1', 'src/app/app.component.ts (ngFor)')
+makeExample('toh-2/ts/app/app.component.1.html', 'heroes-ngfor-1', 'app.component.ts (ngFor)')
.alert.is-critical
:marked
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
.l-sub-section
:marked
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
The (`*`) prefix to `ngFor` is a critical part of this syntax.
It indicates that the `<li>` element and its children
constitute a master template.
The `ngFor` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
and stamps out instances of this template.
The `ngFor` directive iterates over the component's `heroes` array
and renders an instance of this template for each hero in that array.
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 `let hero` part of the expression identifies `hero` as the template input variable,
which holds the current hero item for each iteration.
You can reference this variable within the template to access the current hero's properties.
The `let` keyword before "hero" identifies `hero` as a template input variable.
We can reference this variable within the template to access a heros properties.
Learn more about `ngFor` and template input variables in the
[Displaying Data](../guide/displaying-data.html#ngFor) and
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
Read more about `ngFor` and template input variables in the
[Showing an array property with *ngFor](../guide/displaying-data.html#ngFor) section of the
[Displaying Data](../guide/displaying-data.html) page and the
[ngFor](../guide/template-syntax.html#ngFor) section of the
[Template Syntax](../guide/template-syntax.html) page.
:marked
Now we insert some content between the `<li>` tags
that uses the `hero` template variable to display the heros properties.
Within the `<li>` tags, add content
that uses the `hero` template variable to display the hero's properties.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'ng-for', 'src/app/app.component.ts (ngFor template)')(format=".")
+makeExample('toh-2/ts/app/app.component.1.html', 'ng-for', 'app.component.ts (ngFor template)')(format=".")
:marked
When the browser refreshes, we see a list of heroes!
When the browser refreshes, a list of heroes appears.
### Styling our heroes
Our list of heroes looks pretty bland.
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
### Style the heroes
Users should get a visual cue of which hero they are hovering over and which hero is selected.
Lets add some styles to our component by setting the `styles` property on the `@Component` decorator
To add styles to your component, set the `styles` property on the `@Component` decorator
to the following CSS classes:
+makeExample('toh-2/ts/src/app/app.component.ts', 'styles', 'src/app/app.component.ts (styles)')(format=".")
:marked
Notice that we again use the back-tick notation for multi-line strings.
Remember to use the backtick notation for multi-line strings.
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
We'll do this in a later chapter. For now let's keep rolling.
Adding these styles makes the file much longer. In a later page you'll move the styles to a separate file.
When we assign styles to a component they are scoped to that specific component.
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
When you assign styles to a component, they are scoped to that specific component.
These styles apply only to the `AppComponent` and don't affect the outer HTML.
Our template for displaying the heroes should now look like this:
The template for displaying heroes should look like this:
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'heroes-styled', 'src/app/app.component.ts (styled heroes)')
+makeExample('toh-2/ts/app/app.component.1.html', 'heroes-styled', 'src/app/app.component.ts (styled heroes)')(format='.')
.l-main-section
:marked
## Selecting a Hero
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".
In our case, the master is the heroes list and the detail is the selected hero.
## Selecting a hero
The app now displays a list of heroes as well as a single hero in the details view. But
the list and the details view are not connected.
When users select a hero from the list, the selected hero should appear in the details view.
This UI pattern is known as "master/detail."
In this 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.
Next you'll connect the master to the detail through a `selectedHero` component property,
which is bound to a click event.
### Click event
We modify the `<li>` by inserting an Angular event binding to its click event.
### Add a click event
Add a click event binding to the `<li>` like this:
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'selectedHero-click', 'src/app/app.component.ts (template excerpt)')
+makeExample('toh-2/ts/app/app.component.1.html', 'selectedHero-click', 'app.component.ts (template excerpt)')(format='.')
:marked
Focus on the event binding
code-example.
(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 template input variable `hero` as an argument.
Thats the same `hero` variable we defined previously in the `ngFor`.
The parentheses identify the `<li>` element's `click` event as the target.
The `onSelect(hero)` expression calls the `AppComponent` method, `onSelect()`,
passing the template input variable `hero`, as an argument.
That's the same `hero` variable you defined previously in the `ngFor` directive.
.l-sub-section
:marked
Learn more about Event Binding in the
[User Input](../guide/user-input.html) and
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.
Learn more about event binding at the
[User Input](../guide/user-input.html) page and the
[Event binding](../guide/template-syntax.html#event-binding) section of the
[Template Syntax](../guide/template-syntax.html) page.
:marked
### Add the click handler
Our event binding refers to an `onSelect` method that doesnt exist yet.
Well add that method to our component now.
What should that method do? It should set the components selected hero to the hero that the user clicked.
Our component doesnt have a “selected hero” yet either. Well start there.
### Expose the selected hero
We no longer need the static `hero` property of the `AppComponent`.
**Replace** it with this simple `selectedHero` property:
### Add a click handler to expose the selected hero
You no longer need the `hero` property because you're no longer displaying a single hero; you're displaying a list of heroes.
But the user will be able to select one of the heroes by clicking on it.
So replace the `hero` property with this simple `selectedHero` property:
+makeExample('toh-2/ts/src/app/app.component.ts', 'selected-hero', 'src/app/app.component.ts (selectedHero)')
:marked
The hero names should all be unselected before the user picks a hero, so
you won't initialize the `selectedHero` as you did with `hero`.
Add an `onSelect` method that sets the `selectedHero` property to the `hero` that the user clicks.
+makeExample('toh-2/ts/src/app/app.component.ts', 'on-select', 'src/app/app.component.ts (onSelect)')(format='.')
: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`.
The template still refers to the old `hero` property.
Bind to the new selectedHero property instead as follows:
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
+makeExample('toh-2/ts/src/app/app.component.ts', 'on-select', 'src/app/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.
+makeExample('toh-2/ts/app/app.component.1.html', 'selectedHero-details', 'app.component.ts (template excerpt)')(format='.')
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'selectedHero-details', 'src/app/app.component.ts (template excerpt)')
:marked
### 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`.
Thats why we'll see the following error in the browsers console:
When the app loads, the `selectedHero` is undefined and won't be defined until you click a hero's name.
Angular can't display properties of the undefined `selectedHero` and throws the following error,
visible in the browser's console:
code-example(format="nocode").
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
:marked
Remember that we are displaying `selectedHero.name` in the template.
This name property does not exist because `selectedHero` itself is undefined.
Although `selectedHero.name` is displayed in the template,
you must keep the hero detail out of the DOM until there is a selected hero.
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
Wrap the HTML hero detail content of the template with a `<div>`.
Then add the `ngIf` built-in directive and set it to the `selectedHero` property of the component.
We wrap the HTML hero detail content of our template with a `<div>`.
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'ng-if', 'src/app/app.component.ts (ngIf)')
+makeExample('toh-2/ts/app/app.component.1.html', 'ng-if', 'src/app/app.component.ts (ngIf)')(format='.')
.alert.is-critical
:marked
Remember that the leading asterisk (`*`) in front of `ngIf` is
a critical part of this syntax.
Don't forget the asterisk (`*`) in front of `ngIf`.
:marked
The app no longer fails and the list of names displays again in the browser.
:marked
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.
There are no hero detail elements or bindings to worry about.
When the user picks a hero, `selectedHero` becomes "truthy" and
When the user picks a hero, `selectedHero` becomes defined and
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
.l-sub-section
:marked
`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.
Read more about `ngIf` and `ngFor` in the
[Structural Directives](../guide/structural-directives.html) page and the
[Built-in directives](../guide/template-syntax.html#directives) section of the
[Template Syntax](../guide/template-syntax.html) page.
Learn more about `ngIf`, `ngFor` and other structural directives in the
[Structural Directives](../guide/structural-directives.html) and
[Template Syntax](../guide/template-syntax.html#directives) chapters.
:marked
The browser refreshes and we see the list of heroes but not the selected hero detail.
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.
### Style the selected hero
### Styling the selection
While the selected hero details appear below the list, it's difficult to identify the selected hero within the list itself.
We see the selected hero in the details area below but we cant quickly locate that hero in the list above.
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
For example, when we select Magneta from the heroes list,
we can make it pop out visually by giving it a subtle background color as shown here.
In the `styles` metadata that you added above, there is a custom CSS class named `selected`.
To make the selected hero more visible, you'll apply this `selected` class to the `<li>` when the user clicks on a hero name.
For example, when the user clicks "Magneta", it should render with a distinctive but subtle background color
like this:
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
:marked
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*”.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'class-selected-1', 'src/app/app.component.ts (setting the CSS class)')(format=".")
In the template, add the following `[class.selected]` binding to the `<li>`:
+makeExample('toh-2/ts/app/app.component.1.html', 'class-selected-1', 'app.component.ts (setting the CSS class)')(format=".")
:marked
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 expression `hero === selectedHero`) to a property of `class`.
+makeExample('toh-2/ts-snippets/app.component.snippets.pt2.ts', 'class-selected-2', 'src/app/app.component.ts (styling each hero)')(format=".")
When the expression (`hero === selectedHero`) is `true`, Angular adds the `selected` CSS class.
When the expression is `false`, Angular removes the `selected` class.
.l-sub-section
:marked
Learn more about [property bindings](../guide/template-syntax.html#property-binding)
in the Template Syntax chapter.
Read more about the `[class]` binding in the [Template Syntax](../guide/template-syntax.html#ngClass "Template syntax: NgClass") guide.
:marked
The browser reloads our app.
We select the hero Magneta and the selection is clearly identified by the background color.
The final version of the `<li>` looks like this:
+makeExample('toh-2/ts/app/app.component.1.html', 'class-selected-2', 'app.component.ts (styling each hero)')(format=".")
:marked
After clicking "Magneta", the list should look like this:
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
:marked
We select a different hero and the tell-tale color switches to that hero.
Here's the complete `app.component.ts` as it stands now:
Here's the complete `app.component.ts` as of now:
+makeExample('toh-2/ts/src/app/app.component.ts', '', 'src/app/app.component.ts')
.l-main-section
:marked
## The Road Weve Travelled
Heres what we achieved in this chapter:
## The road you've travelled
Here's what you achieved in this page:
* 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 `ngIf` and `ngFor` in a components template
* The Tour of Heroes app displays a list of selectable heroes.
* You added the ability to select a hero and show the hero's details.
* You learned how to use the built-in directives `ngIf` and `ngFor` in a component's template.
Run the <live-example></live-example> for this part.
Your app should look like this <live-example></live-example>.
### The Road Ahead
Our Tour of Heroes has grown, but its far from complete.
We can't put the entire app into a single component.
We need to break it up into sub-components and teach them to work together
as we learn in the [next chapter](toh-pt3.html).
## The road ahead
You've expanded the Tour of Heroes app, but it's far from complete.
You can't put the entire app into a single component.
In the [next page](toh-pt3.html), you'll split the app into sub-components and make them work together.

View File

@ -1,15 +1,23 @@
include ../_util-fns
:marked
Our app is growing.
Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
The `AppComponent` is doing _everything_ at the moment.
In the beginning, it showed details of a single hero.
Then it became a master/detail form with both a list of heroes and the hero detail.
Soon there will be new requirements and capabilities.
You can't keep piling features on top of features in one component; that's not maintainable.
Run the <live-example></live-example> for this part.
You'll ned to break it up into sub-components, each focused on a specific task or workflow.
Eventually, the `AppComponent` could become a simple shell that hosts those sub-components.
In this page, you'll take the first step in that direction by carving out the hero details into a separate, reusable component.
When you're done, the app should look like this <live-example></live-example>.
.l-main-section
:marked
## 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.
## Where you left off
Before getting started on this page, verify that you have the following structure from earlier in the Tour of Heroes.
If not, go back to the previous pages.
.filetree
.file angular-tour-of-heroes
@ -25,182 +33,178 @@ include ../_util-fns
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file node_modules ...
.file 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
Keep the app transpiling and running while you build the Tour of Heroes
by entering the `npm start` command in a terminal window
[as you did before](toh-pt1.html#keep-transpiling "Keep the app running").
code-example(language="sh" class="code-shell").
npm start
## Make a hero detail component
Add a file named `hero-detail.component.ts` to the `app/` folder.
This file will hold the new `HeroDetailComponent`.
The file and component names follow the standard described in the Angular
[style guide](../guide/style-guide.html#naming).
* The component _class_ name should be written in _upper camel case_ and end in the word "Component".
The hero detail component class is `HeroDetailComponent`.
* The component _file_ name should be spelled in [_lower dash case_](../guide/glossary.html#dash-case),
each word separated by dashes, and end in `.component.ts`.
The `HeroDetailComponent` class goes in the `hero-detail.component.ts` file.
Start writing the `HeroDetailComponent` as follows:
+makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'v1', 'app/hero-detail.component.ts (initial version)')(format=".")
a#selector
:marked
This will keep the application running while we continue to build the Tour of Heroes.
To define a component, you always import the `Component` symbol.
## Making a Hero Detail Component
Our heroes list and our hero details are in the same component in the same file.
They're small now but each could grow.
We are sure to receive new requirements for one and not the other.
Yet every change puts both components at risk and doubles the testing burden without benefit.
If we had to reuse the hero details elsewhere in our app,
the heroes list would tag along for the ride.
Our current component violates the
[Single Responsibility Principle](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
It's only a tutorial but we can still do things right &mdash;
especially if doing them right is easy and we learn how to build Angular apps in the process.
Lets break the hero details out into its own component.
The `@Component` decorator provides the Angular metadata for the component.
The CSS selector name, `hero-detail`, will match the element tag
that identifies this component within a parent component's template.
[Near the end of this tutorial page](#add-hero-detail "Add the HeroDetailComponent to the AppComponent"),
you'll add a `<hero-detail>` element to the `AppComponent` template.
### Separating the Hero Detail Component
Add a new file named `hero-detail.component.ts` to the `app` folder and create `HeroDetailComponent` as follows.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'v1', 'src/app/hero-detail.component.ts (initial version)')(format=".")
.l-sub-section
:marked
### Naming conventions
We like to identify at a glance which classes are components and which files contain components.
Notice that we have an `AppComponent` in a file named `app.component.ts` and our new
`HeroDetailComponent` is in a file named `hero-detail.component.ts`.
All of our component names end in "Component". All of our component file names end in ".component".
We spell our file names in lower **[dash case](../guide/glossary.html#dash-case)**
(AKA **[kebab-case](../guide/glossary.html#kebab-case)**) so we don't worry about
case sensitivity on the server or in source control.
<!-- TODO
.l-sub-section
:marked
Learn more about naming conventions in the chapter [Naming Conventions]
:marked
-->
Always `export` the component class because you'll always `import` it elsewhere.
:marked
We begin by importing the `Component` and `Input` decorators from Angular because we're going to need them soon.
We create metadata with the `@Component` decorator where we
specify the selector name that identifies this component's element.
Then we export the class to make it available to other components.
When we finish here, we'll import it into `AppComponent` and create a corresponding `<my-hero-detail>` element.
:marked
#### Hero Detail Template
At the moment, the *Heroes* and *Hero Detail* views are combined in one template in `AppComponent`.
Lets **cut** the *Hero Detail* content from `AppComponent` and **paste** it into the new template property of `HeroDetailComponent`.
### Hero detail template
To move the hero detail view to the `HeroDetailComponent`,
cut the hero detail _content_ from the bottom of the `AppComponent` template
and paste it into a new `template` property in the `@Component` metadata.
We previously bound to the `selectedHero.name` property of the `AppComponent`.
Our `HeroDetailComponent` will have a `hero` property, not a `selectedHero` property.
So we replace `selectedHero` with `hero` everywhere in our new template. That's our only change.
The result looks like this:
The `HeroDetailComponent` has a _hero_, not a _selected hero_.
Replace the word, "selectedHero", with the word, "hero", everywhere in the template.
When you're done, the new template should look like this:
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'template', 'src/app/hero-detail.component.ts (template)')(format=".")
:marked
Now our hero detail layout exists only in the `HeroDetailComponent`.
#### Add the *hero* property
Lets add that `hero` property we were talking about to the component class.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'hero')
:marked
Uh oh. We declared the `hero` property as type `Hero` but our `Hero` class is over in the `app.component.ts` file.
We have two components, each in their own file, that need to reference the `Hero` class.
We solve the problem by relocating the `Hero` class from `app.component.ts` to its own `hero.ts` file.
### Add the *hero* property
The `HeroDetailComponent` template binds to the component's `hero` property.
Add that property to the `HeroDetailComponent` class like this:
+makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'hero', 'src/app/hero-detail.component.ts (hero property)')
:marked
The `hero` property is typed as an instance of `Hero`.
The `Hero` class is still in the `app.component.ts` file.
Now there are two components that need to reference the `Hero` class.
The Angular [style guide](../guide/style-guide.html#rule-of-one "Style guide: rule of one") recommends one class per file anyway.
Move the `Hero` class from `app.component.ts` to its own `hero.ts` file.
+makeExample('toh-3/ts/src/app/hero.ts', '', 'src/app/hero.ts')(format=".")
:marked
We export the `Hero` class from `hero.ts` because we'll need to reference it in both component files.
Add the following import statement near the top of **both `app.component.ts` and `hero-detail.component.ts`**.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'hero-import')
Now that the `Hero` class is in its own file, the `AppComponent` and the `HeroDetailComponent` have to import it.
Add the following `import` statement near the top of _both_ the `app.component.ts` and the `hero-detail.component.ts` files.
+makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'hero-import')
:marked
#### The *hero* property is an ***input***
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
The user's selection is in its `selectedHero` property.
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
code-example(language="html").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
### The *hero* property is an *input* property
[Later in this page](#add-hero-detail "Add the HeroDetailComponent to the AppComponent"),
the parent `AppComponent` will tell the child `HeroDetailComponent` which hero to display
by binding its `selectedHero` to the `hero` property of the `HeroDetailComponent`.
The binding will look like this:
+makeExample('toh-3/ts/app/app.component.1.html', 'hero-detail-binding')(format='.')
:marked
Notice that the `hero` property is the ***target*** of a property binding &mdash; it's in square brackets to the left of the (=).
Angular insists that we declare a ***target*** property to be an ***input*** property.
If we don't, Angular rejects the binding and throws an error.
Putting square brackets around the `hero` property, to the left of the equal sign (=),
makes it the *target* of a property binding expression.
You must declare a *target* binding property to be an *input* property.
Otherwise, Angular rejects the binding and throws an error.
First, amend the `@angular/core` import statement to include the `Input` symbol.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'import-input', 'src/app/hero-detail.component.ts (excerpt)')(format='.')
:marked
Then declare that `hero` is an *input* property by
preceding it with the `@Input` decorator that you imported earlier.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'hero', 'src/app/hero-detail.component.ts (excerpt)')(format='.')
.l-sub-section
:marked
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
where we also explain why *target* properties require this special treatment and
*source* properties do not.
Read more about _input_ properties in the
[Attribute Directives](../guide/attribute-directives.html#why-input) page.
:marked
That's it. The `hero` property is the only thing in the `HeroDetailComponent` class.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'class')(format='.')
:marked
There are a couple of ways we can declare that `hero` is an *input*.
We'll do it the way we *prefer*, by annotating the `hero` property with the `@Input` decorator that we imported earlier.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'hero-input')(format='.')
.l-sub-section
:marked
Learn more about the `@Input()` decorator in the
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
All it does is receive a hero object through its `hero` input property and then bind to that property with its template.
Here's the complete `HeroDetailComponent`.
+makeExample('toh-3/ts/src/app/hero-detail.component.ts', '', 'src/app/hero-detail.component.ts')
.l-main-section
:marked
## Refresh the AppModule
We return to the `AppModule`, the application's root module, and teach it to use the `HeroDetailComponent`.
## Declare _HeroDetailComponent_ in the _AppModule_
Every component must be declared in one&mdash;and only one&mdash;Angular module.
We begin by importing the `HeroDetailComponent` so we can refer to it.
+makeExample('toh-3/ts/src/app/app.module.ts', 'hero-detail-import')
Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it.
+makeExample('toh-3/ts/src/app/app.module.ts', 'hero-detail-import', 'src/app/app.module.ts')
:marked
Then we add `HeroDetailComponent` to the `NgModule` decorator's `declarations` array.
This array contains the list of all components, pipes, and directives that we created
and that belong in our application's module.
Add `HeroDetailComponent` to the module's `declarations` array.
+makeExample('toh-3/ts/src/app/app.module.ts', 'declarations')
+makeExample('toh-3/ts/src/app/app.module.ts', 'declarations', 'src/app/app.module.ts')(format='.')
:marked
In general, the `declarations` array contains a list of application components, pipes, and directives that belong to the module.
A component must be declared in a module before other components can reference it.
This module declares only the two application components, `AppComponent` and `HeroDetailComponent`.
.l-sub-section
:marked
Read more about Angular modules in the [NgModules](../guide/ngmodule.html "Angular Modules (NgModule) guide.
a#add-hero-detail
.l-main-section
:marked
## Add the _HeroDetailComponent_ to the _AppComponent_
:marked
The `AppComponent` is still a master/detail view.
It used to display the hero details on its own, before you cut out that portion of the template.
Now it will delegate to the `HeroDetailComponent`.
Recall that `hero-detail` is the CSS [`selector`](#selector "HeroDetailComponent selector")
in the `HeroDetailComponent` metadata.
That's the tag name of the element that represents the `HeroDetailComponent`.
Add a `<hero-detail>` element near the bottom of the `AppComponent` template,
where the hero detail view used to be.
Coordinate the master `AppComponent` with the `HeroDetailComponent`
by binding the `selectedHero` property of the `AppComponent`
to the `hero` property of the `HeroDetailComponent`.
+makeExample('toh-3/ts/app/app.component.1.html', 'hero-detail-binding', 'app.component.html (excerpt)')(format='.')
:marked
Now every time the `selectedHero` changes, the `HeroDetailComponent` gets a new hero to display.
The revised `AppComponent` template should look like this:
+makeExample('toh-3/ts/src/app/app.component.ts', 'hero-detail-template', 'app.component.ts (excerpt)')(format='.')
.l-main-section
:marked
## Refresh the AppComponent
:marked
Now that the application knows about our `HeroDetailComponent`,
find the location in the `AppComponent` template where we removed the *Hero Detail* content
and add an element tag that represents the `HeroDetailComponent`.
code-example(language="html").
&lt;my-hero-detail>&lt;/my-hero-detail>
.l-sub-section
:marked
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.
:marked
The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
to the `HeroDetailComponent` element's `hero` property like this:
code-example(language="html").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
The `AppComponent`s template should now look like this
## What changed?
As [before](./toh-pt2.html), whenever a user clicks on a hero name,
the hero detail appears below the hero list.
But now the `HeroDetailView` is presenting those details.
+makeExample('toh-3/ts/src/app/app.component.ts', 'hero-detail-template', 'app.component.ts (template)')(format='.')
:marked
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
The detail should update every time the user picks a new hero.
:marked
### It works!
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.
What's fundamentally new is that we can use this `HeroDetailComponent`
to show hero details anywhere in the app.
Refactoring the original `AppComponent` into two components yields benefits, both now and in the future:
Weve created our first reusable component!
1. You simplified the `AppComponent` by reducing its responsibilities.
### Reviewing the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
1. You can evolve the `HeroDetailComponent` into a rich hero editor
without touching the parent `AppComponent`.
1. You can evolve the `AppComponent` without touching the hero detail view.
1. You can re-use the `HeroDetailComponent` in the template of some future parent component.
### Review the app structure
Verify that you have the following structure:
.filetree
.file angular-tour-of-heroes
@ -220,8 +224,9 @@ code-example(language="html").
.file tsconfig.json
.file node_modules ...
.file package.json
:marked
Here are the code files we discussed in this chapter.
Here are the code files discussed in this page.
+makeTabs(`
toh-3/ts/src/app/hero-detail.component.ts,
@ -237,25 +242,24 @@ code-example(language="html").
.l-main-section
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
## The road youve travelled
Here's what you achieved in this page:
* We created a reusable component
* We learned how to make a component accept input
* We learned to declare the application directives we need in an Angular module. We
list the directives in the `NgModule` decorator's `declarations` array.
* We learned to bind a parent component to a child component.
* You created a reusable component.
* You learned how to make a component accept input.
* You learned to declare the required application directives in an Angular module. You
listed the directives in the `NgModule` decorator's `declarations` array.
* You learned to bind a parent component to a child component.
Run the <live-example></live-example> for this part.
Your app should look like this <live-example></live-example>.
.l-main-section
:marked
## The Road Ahead
Our Tour of Heroes has become more reusable with shared components.
We're still getting our (mock) data within the `AppComponent`.
That's not sustainable.
We should refactor data access to a separate service
and share it among the components that need data.
Well learn to create services in the [next tutorial](toh-pt4.html) chapter.
## The road ahead
The Tour of Heroes app is more reusable with shared components,
but its (mock) data is still hard coded within the `AppComponent`.
That's not sustainable.
Data access should be refactored to a separate service
and shared among the components that need data.
Youll learn to create services in the [next tutorial](toh-pt4.html) page.

View File

@ -1,26 +1,24 @@
include ../_util-fns
:marked
The Tour of Heroes is evolving and we anticipate adding more components in the near future.
Multiple components will need access to hero data and we don't want to copy and
paste the same code over and over.
Instead, we'll create a single reusable data service and learn to
inject it in the components that need it.
Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
It also makes it easier to unit test the component with a mock service.
Because data services are invariably asynchronous,
we'll finish the chapter with a **!{_Promise}**-based version of the data service.
As the Tour of Heroes app evolves, you'll add more components that need access to hero data.
Run the <live-example></live-example> for this part.
Instead of copying and pasting the same code over and over,
you'll create a single reusable data service and
inject it into the components that need it.
Using a separate service keeps components lean and focused on supporting the view,
and makes it easy to unit-test components with a mock service.
Because data services are invariably asynchronous,
you'll finish the page with a *!{_Promise}*-based version of the data service.
When you're done with this page, the app should look like this <live-example></live-example>.
.l-main-section
:marked
## 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.
## Where you left off
Before continuing with the Tour of Heroes, verify that you have the following structure.
If not, go back to the previous pages.
.filetree
.file angular-tour-of-heroes
@ -42,265 +40,252 @@ include ../_util-fns
.file package.json
:marked
### Keep the app transpiling and running
Open a terminal/console window.
Start the TypeScript compiler, watch for changes, and start our server by entering the command:
## Keep the app transpiling and running
Enter the following command in the terminal window:
code-example(language="sh" class="code-shell").
npm start
:marked
The application runs and updates automatically as we continue to build the Tour of Heroes.
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
## 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 on different pages.
We already can select a hero from a list.
Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
All three views need hero data.
At the moment the `AppComponent` defines mock heroes for display.
We have at least two objections.
First, defining heroes is not the component's job.
Second, we can't easily share that list of heroes with other components and views.
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
We can refactor this hero data acquisition business to a single service that provides heroes, and
share that service with all components that need heroes.
## Creating a hero service
The stakeholders want to show the heroes in various ways on different pages.
Users can already select a hero from a list.
Soon you'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
All three views need hero data.
At the moment, the `AppComponent` defines mock heroes for display.
However, defining heroes is not the component's job,
and you can't easily share the list of heroes with other components and views.
In this page, you'll move the hero data acquisition business to a single service that provides the data and
share that service with all components that need the data.
### Create the HeroService
Create a file in the `app` folder called `hero.service.ts`.
Create a file in the `app` folder called `hero.service.ts`.
.l-sub-section
:marked
We've adopted a convention in which we spell the name of a service in lowercase followed by `.service`.
If the service name were multi-word, we'd spell the base filename in lower [dash-case](../guide/glossary.html#dash-case).
The `SpecialSuperHeroService` would be defined in the `special-super-hero.service.ts` file.
The naming convention for service files is the service name in lowercase followed by `.service`.
For a multi-word service name, use lower [dash-case](../guide/glossary.html#!#dash-case).
For example, the filename for `SpecialSuperHeroService` is `special-super-hero.service.ts`.
:marked
We name the class `HeroService` and export it for others to import.
Name the class `HeroService` and export it for others to import.
+makeExample('toh-4/ts/src/app/hero.service.1.ts', 'empty-class', 'src/app/hero.service.ts (starting point)')(format=".")
:marked
### Injectable Services
Notice that we imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
### Injectable services
Notice that you imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
.callout.is-helpful
:marked
**Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
Don't forget the parentheses. Omitting them leads to an error that's difficult to diagnose.
:marked
TypeScript sees the `@Injectable()` decorator and emits metadata about our service,
metadata that Angular may need to inject other dependencies into this service.
The `@Injectable()` decorator tells TypeScript to emit metadata about the service.
The metadata specifies that Angular may need to inject other dependencies into this service.
The `HeroService` doesn't have any dependencies *at the moment*. Add the decorator anyway.
It is a "best practice" to apply the `@Injectable()` decorator *from the start*
both for consistency and for future-proofing.
Although the `HeroService` doesn't have any dependencies at the moment,
applying the `@Injectable()` decorator from the start ensures
consistency and future-proofing.
:marked
### Getting Heroes
### Getting hero data
Add a `getHeroes` method stub.
+makeExample('toh-4/ts/src/app/hero.service.1.ts', 'getHeroes-stub', 'src/app/hero.service.ts (getHeroes stub)')(format=".")
:marked
We're holding back on the implementation for a moment to make an important point.
The consumer of our service doesn't know how the service gets the data.
Our `HeroService` could get `Hero` data from anywhere.
It could get the data from a web service or local storage
or from a mock data source.
That's the beauty of removing data access from the component.
We can change our minds about the implementation as often as we like,
for whatever reason, without touching any of the components that need heroes.
### Mock Heroes
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
We'll move the mock data to its own file.
+makeExample('toh-4/ts/src/app/hero.service.1.ts', 'getHeroes-stub', 'src/app/hero.service.ts (getHeroes stub)')(format=".")
:marked
The `HeroService` could get `Hero` data from anywhere&mdash;a
web service, local storage, or a mock data source.
Removing data access from the component means
you can change your mind about the implementation anytime,
without touching the components that need hero data.
### Move the mock hero data
Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`.
We copy the `import {Hero} ...` statement as well because the heroes array uses the `Hero` class.
Additionally, copy the `import {Hero} ...` statement because the heroes array uses the `Hero` class.
+makeExample('toh-4/ts/src/app/mock-heroes.ts', null, 'src/app/mock-heroes.ts')
:marked
We export the `HEROES` constant so we can import it elsewhere &mdash; such as our `HeroService`.
The `HEROES` constant is exported so it can be imported elsewhere, such as the `HeroService`.
Meanwhile, back in `app.component.ts` where we cut away the `HEROES` array,
we leave behind an uninitialized `heroes` property:
In `app.component.ts`, where you cut the `HEROES` array,
add an uninitialized `heroes` property:
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'heroes-prop', 'src/app/app.component.ts (heroes property)')(format=".")
:marked
### Return Mocked Heroes
Back in the `HeroService` we import the mock `HEROES` and return it from the `getHeroes` method.
Our `HeroService` looks like this:
### Return mocked hero data
Back in the `HeroService`, import the mock `HEROES` and return it from the `getHeroes` method.
The `HeroService` looks like this:
+makeExample('toh-4/ts/src/app/hero.service.1.ts', 'full', 'src/app/hero.service.ts')(format=".")
:marked
### Use the Hero Service
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
We begin, as usual, by importing the thing we want to use, the `HeroService`.
+makeExcerpt('toh-4/ts/src/app/app.component.ts', 'hero-service-import')
:marked
Importing the service allows us to *reference* it in our code.
### Import the hero service
You're ready to use the `HeroService` in other components, starting with `AppComponent`.
Import the `HeroService` so that you can reference it in the code.
+makeExcerpt('toh-4/ts/src/app/app.component.ts', 'hero-service-import', 'src/app/app.component.ts')
:marked
### Don't use *new* with the *HeroService*
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
### Do we *new* the *HeroService*? No way!
We could create a new instance of the `HeroService` with `new` like this:
You could create a new instance of the `HeroService` with `new` like this:
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'new-service')(format=".")
:marked
That's a bad idea for several reasons including
* Our component has to know how to create a `HeroService`.
If we ever change the `HeroService` constructor,
we'll have to find every place we create the service and fix it.
Running around patching code is error prone and adds to the test burden.
* We create a new service each time we use `new`.
What if the service should cache heroes and share that cache with others?
We couldn't do that.
* We're locking the `AppComponent` into a specific implementation of the `HeroService`.
It will be hard to switch implementations for different scenarios.
Can we operate offline?
Will we need different mocked versions under test?
Not easy.
*What if ... what if ... Hey, we've got work to do!*
We get it. Really we do.
But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
However, this option isn't ideal for the following reasons:
* The component has to know how to create a `HeroService`.
If you change the `HeroService` constructor,
you must find and update every place you created the service.
Patching code in multiple places is error prone and adds to the test burden.
* You create a service each time you use `new`.
What if the service caches heroes and shares that cache with others?
You couldn't do that.
* With the `AppComponent` locked into a specific implementation of the `HeroService`,
switching implementations for different scenarios, such as operating offline or using
different mocked versions for testing, would be difficult.
### Inject the *HeroService*
Two lines replace the one line that created with *new*:
1. We add a constructor that also defines a private property.
1. We add to the component's `providers` metadata.
Here's the constructor:
Instead of using the *new* line, you'll add two lines.
* Add a constructor that also defines a private property.
* Add to the component's `providers` metadata.
Add the constructor:
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'ctor', 'src/app/app.component.ts (constructor)')
:marked
The constructor itself does nothing. The parameter simultaneously
The constructor itself does nothing. The parameter simultaneously
defines a private `heroService` property and identifies it as a `HeroService` injection site.
:marked
Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
Now Angular knows to supply an instance of the `HeroService` when it creates an `AppComponent`.
.l-sub-section
:marked
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
Read more about dependency injection in the [Dependency Injection](../guide/dependency-injection.html) page.
:marked
The *injector* does not know yet how to create a `HeroService`.
If we ran our code now, Angular would fail with an error:
The *injector* doesn't know yet how to create a `HeroService`.
If you ran the code now, Angular would fail with this error:
code-example(format="nocode").
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
:marked
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
Do that by adding the following `providers` array property to the bottom of the component metadata
To teach the injector how to make a `HeroService`,
add the following `providers` array property to the bottom of the component metadata
in the `@Component` call.
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'providers')
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'providers', 'src/app/app.component.ts')
:marked
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates an `AppComponent`.
The `AppComponent`, as well as its child components, can use that service to get hero data.
a#child-component
:marked
### *getHeroes* in the *AppComponent*
We've got the service in a `heroService` private variable. Let's use it.
We pause to think. We can call the service and get the data in one line.
The service is in a `heroService` private variable.
You could call the service and get the data in one line.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
:marked
We don't really need a dedicated method to wrap one line. We write it anyway:
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'getHeroes')
You don't really need a dedicated method to wrap one line. Write it anyway:
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts')
<a id="oninit"></a>
:marked
### The *ngOnInit* Lifecycle Hook
`AppComponent` should fetch and display heroes without a fuss.
Where do we call the `getHeroes` method? In a constructor? We do *not*!
Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
especially anything that might call a server as a data access method is sure to do.
The constructor is for simple initializations like wiring constructor parameters to properties.
It's not for heavy lifting. We should be able to create a component in a test and not worry that it
might do real work &mdash; like calling a server! &mdash; before we tell it to do so.
If not the constructor, something has to call `getHeroes`.
Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
### The *ngOnInit* lifecycle hook
`AppComponent` should fetch and display hero data with no issues.
You might be tempted to call the `getHeroes` method in a constructor, but
a constructor should not contain complex logic,
especially a constructor that calls a server, such as as a data access method.
The constructor is for simple initializations, like wiring constructor parameters to properties.
To have Angular call `getHeroes`, you can implement the Angular *ngOnInit lifecycle hook*.
Angular offers interfaces for tapping into critical moments in the component lifecycle:
at creation, after each change, and at its eventual destruction.
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
.l-sub-section
:marked
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
Read more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) page.
:marked
Here's the essential outline for the `OnInit` interface:
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'on-init', 'src/app/app.component.ts (ngOnInit stub)')(format=".")
:marked
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
at the right time. In our case, we initialize by calling `getHeroes`.
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'ng-on-init')
:marked
Our application should be running as expected, showing a list of heroes and a hero detail view
when we click on a hero name.
We're getting closer. But something isn't quite right.
Here's the essential outline for the `OnInit` interface (don't copy this into your code):
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'on-init')(format=".")
<a id="async"></a>
## Async Services and !{_Promise}s
Our `HeroService` returns a list of mock heroes immediately.
Its `getHeroes` signature is synchronous
:marked
Add the implementation for the `OnInit` interface to your export statement:
code-example(format="nocode").
export class AppComponent implements OnInit {}
:marked
Write an `ngOnInit` method with the initialization logic inside. Angular will call it
at the right time. In this case, initialize by calling `getHeroes`.
+makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'ng-on-init', 'app/app.component.ts')
:marked
The app should run as expected, showing a list of heroes and a hero detail view
when you click on a hero name.
<a id="async"></a>
:marked
## Async services and !{_Promise}s
The `HeroService` returns a list of mock heroes immediately;
its `getHeroes` signature is synchronous.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".")
:marked
Ask for heroes and they are there in the returned result.
Someday we're going to get heroes from a remote server. We dont call http yet, but we aspire to in later chapters.
When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
even if we want to (which we shouldn't) because the browser won't block.
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
We'll use *!{_Promise}s*.
### The Hero Service makes a !{_Promise}
Eventually, the hero data will come from a remote server.
When using the remote server, users don't have to wait for the server to respond
and you aren't able to block the UI during the wait.
A **!{_Promise}** is ... well it's a promise to call us back later when the results are ready.
We ask an asynchronous service to do some work and give it a callback function.
It does that work (somewhere) and eventually it calls our function with the results of the work or an error.
:marked
Instead, you can use *!{_Promise}s*, which is an asynchronous technique that changes the signature of the `getHeroes` method.
### The hero service makes a !{_Promise}
A *!{_Promise}* essentially promises to call back when the results are ready.
You ask an asynchronous service to do some work and give it a callback function.
The service does that work and eventually calls the function with the results or an error.
.l-sub-section
:marked
We are simplifying. Learn about ES2015 Promises [here](http://exploringjs.com/es6/ch_promises.html) and elsewhere on the web.
This is a simplified explanation. Read more about ES2015 Promises in the
[Promises for asynchronous programming](http://exploringjs.com/es6/ch_promises.html) page of
[Exploring ES6](http://http://exploringjs.com/es6.html).
:marked
Update the `HeroService` with this !{_Promise}-returning `getHeroes` method:
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".")
:marked
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
by returning an **immediately resolved !{_Promise}** with our mock heroes as the result.
You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server,
by returning an *immediately resolved !{_Promise}* with the mock heroes as the result.
### Act on the !{_Promise}
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
As a result of the change to `HeroService`, `this.heroes` is now set to a !{_Promise} rather than an array of heroes.
+makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".")
:marked
As a result of our change to `HeroService`, we're now setting `this.heroes` to a !{_Promise} rather than an array of heroes.
We have to change our implementation to *act on the !{_Promise} when it resolves*.
When the !{_Promise} resolves successfully, *then* we will have heroes to display.
We pass our callback function as an argument to the !{_Promise}'s **then** method:
You have to change the implementation to *act on the !{_Promise} when it resolves*.
When the !{_Promise} resolves successfully, you'll have heroes to display.
Pass the callback function as an argument to the !{_Promise}'s `then` method:
+makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".")
.l-sub-section
:marked
The [ES2015 arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
in the callback is more succinct than the equivalent function expression and gracefully handles *this*.
As described in [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions),
the ES2015 arrow function
in the callback is more succinct than the equivalent function expression and gracefully handles `this`.
:marked
Our callback sets the component's `heroes` property to the array of heroes returned by the service. That's all there is to it!
Our app should still be running, still showing a list of heroes, and still
The callback sets the component's `heroes` property to the array of heroes returned by the service.
The app is still running, showing a list of heroes, and
responding to a name selection with a detail view.
.l-sub-section
:marked
Checkout the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.
At the end of this page, [Appendix: take it slow](#slow) describes what the app might be like with a poor connection.
:marked
### Review the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
## Review the app structure
Verify that you have the following structure after all of your refactoring:
.filetree
.file angular-tour-of-heroes
@ -320,10 +305,10 @@ a#child-component
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file node_modules ...
.file node_modules ...
.file package.json
:marked
Here are the code files we discussed in this chapter.
Here are the code files discussed in this page.
+makeTabs(`
toh-4/ts/src/app/hero.service.ts,
@ -335,36 +320,34 @@ a#child-component
src/app/mock-heroes.ts
`)
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
## The road you've travelled
Here's what you achieved in this page:
* We created a service class that can be shared by many components.
* We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates.
* We defined our `HeroService` as a provider for our `AppComponent`.
* 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}.
* You created a service class that can be shared by many components.
* You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates.
* You defined the `HeroService` as a provider for the `AppComponent`.
* You created mock hero data and imported them into the service.
* You designed the service to return a !{_Promise} and the component to get the data from the !{_Promise}.
Run the <live-example></live-example> for this part.
Your app should look like this <live-example></live-example>.
## The road ahead
The Tour of Heroes has become more reusable using shared components and services.
The next goal is to create a dashboard, add menu links that route between the views, and format data in a template.
As the app evolves, you'll learn how to design it to make it easier to grow and maintain.
You learn about Angular component router and navigation among the views in the [next tutorial](toh-pt5.html) page.
### The Road Ahead
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.
We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
.l-main-section
<a id="slow"></a>
:marked
### Appendix: Take it slow
We can simulate a slow connection.
Import the `Hero` symbol and add the following `getHeroesSlowly` method to the `HeroService`
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'src/app/hero.service.ts (getHeroesSlowly)')(format=".")
## Appendix: Take it slow
To simulate a slow connection,
import the `Hero` symbol and add the following `getHeroesSlowly` method to the `HeroService`.
+makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".")
:marked
Like `getHeroes`, it also returns a !{_Promise}.
But this !{_Promise} waits 2 seconds before resolving the !{_Promise} with mock heroes.
Like `getHeroes`, it also returns a !{_Promise}.
But this !{_Promise} waits two seconds before resolving the !{_Promise} with mock heroes.
Back in the `AppComponent`, replace `heroService.getHeroes` with `heroService.getHeroesSlowly`
and see how the app behaves.

File diff suppressed because it is too large Load Diff

View File

@ -12,158 +12,154 @@ block includes
- var _promise = _Promise.toLowerCase()
:marked
Our stakeholders appreciate our progress.
Now they want to get the hero data from a server, let users add, edit, and delete heroes,
and save these changes back to the server.
In this page, you'll make the following improvements.
In this chapter we teach our application to make the corresponding HTTP calls to a remote server's web API.
* Get the hero data from a server.
* Let users add, edit, and delete hero names.
* Save the changes to the server.
Run the <live-example></live-example> for this part.
You'll teach the app to make corresponding HTTP calls to a remote server's web API.
When you're done with this page, the app should look like this <live-example></live-example>.
.l-main-section
:marked
## Where We Left Off
In the [previous chapter](toh-pt5.html), we learned to navigate between the dashboard and the fixed heroes list, editing a selected hero along the way.
That's our starting point for this chapter.
## Where you left off
In the [previous page](toh-pt5.html), you learned to navigate between the dashboard and the fixed heroes list,
editing a selected hero along the way.
That's the starting point for this page.
block start-server-and-watch
:marked
### Keep the app transpiling and running
## Keep the app transpiling and running
Enter the following command in the terminal window:
Open a terminal/console window and enter the following command to
start the TypeScript compiler, start the server, and watch for changes:
code-example(language="sh" class="code-shell").
npm start
code-example(language="sh" class="code-shell").
npm start
:marked
The application runs and updates automatically as we continue to build the Tour of Heroes.
This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes.
The command simultaneously launches the app in a browser and refreshes the browser when the code changes.
You can keep building the Tour of Heroes without pausing to recompile or refresh the browser.
.l-main-section#http-providers
h1 Providing HTTP Services
block http-library
:marked
The `HttpModule` is ***not*** a core Angular module.
It's Angular's optional approach to web access and it exists as a separate add-on module called `@angular/http`,
shipped in a separate script file as part of the Angular npm package.
The `HttpModule` is not a core Angular module.
`HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http`
and is shipped in a separate script file as part of the Angular npm package.
Fortunately we're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when we need it.
You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it.
:marked
### Register for HTTP services
block http-providers
:marked
Our app will depend upon the Angular `http` service which itself depends upon other supporting services.
The app will depend on the Angular `http` service, which itself depends on other supporting services.
The `HttpModule` from `@angular/http` library holds providers for a complete set of HTTP services.
We should be able to access these services from anywhere in the application.
So we register them all by adding `HttpModule` to the `imports` list of the `AppModule` where we
bootstrap the application and its root `AppComponent`.
To allow access to these services from anywhere in the app,
add `HttpModule` to the `imports` list of the `AppModule`.
+makeExample('src/app/app.module.ts', 'v1','src/app/app.module.ts (v1)')
:marked
Notice that we supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
Notice that you supply `!{_HttpModule}` as part of the *imports* !{_array} in root NgModule `AppModule`.
.l-main-section
:marked
## Simulating the web API
## Simulate the web API
We recommend registering application-wide services in the root
`!{_AppModuleVsAppComp}` *providers*. <span if-docs="dart">Here we're
We recommend registering app-wide services in the root
`!{_AppModuleVsAppComp}` *providers*. <span if-docs="dart">Here you're
registering in `main` for a special reason.</span>
Our application is in the early stages of development and far from ready for production.
We don't even have a web server that can handle requests for heroes.
Until we do, *we'll have to fake it*.
We're going to *trick* the HTTP client into fetching and saving data from
Until you have a web server that can handle requests for hero data,
the HTTP client will fetch and save data from
a mock service, the *in-memory web API*.
<span if-docs="dart"> The application itself doesn't need to know and
shouldn't know about this. So we'll slip the in-memory web API into the
configuration *above* the `AppComponent`.</span>
<span if-docs="dart"> The app itself doesn't need to know
about this, so you can slip the in-memory web API into the
configuration above the `AppComponent`.</span>
Here is a version of <span ngio-ex>!{_appModuleTsVsMainTs}</span> that performs this trick:
Update <span ngio-ex>!{_appModuleTsVsMainTs}</span> with this version, which uses the mock service:
+makeExcerpt(_appModuleTsVsMainTs, 'v2')
block backend
:marked
Rather than require a real API server, this example simulates communication with the remote server by adding the
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API"><i>InMemoryWebApiModule</i></a>
<a href="https://github.com/angular/in-memory-web-api" target="_blank" title="In-memory Web API">InMemoryWebApiModule</a>
to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative.
+makeExcerpt(_appModuleTsVsMainTs, 'in-mem-web-api', '')
:marked
The `forRoot` configuration method takes an `InMemoryDataService` class
that primes the in-memory database as follows:
that primes the in-memory database.
Add the file `in-memory-data.service.ts` in `!{_appDir}` with the following content:
+makeExample('src/app/in-memory-data.service.ts', 'init')(format='.')
p This file replaces the #[code #[+adjExPath('mock-heroes.ts')]] which is now safe to delete.
:marked
This file replaces the #[code #[+adjExPath('mock-heroes.ts')]], which is now safe to delete.
block dont-be-distracted-by-backend-subst
.alert.is-helpful
:marked
This chapter is an introduction to the !{_Angular_http_library}.
Please don't be distracted by the details of this backend substitution. Just follow along with the example.
The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
Don't worry about the details of this backend substitution; you can
skip it when you have a real web API server.
Learn more later about the in-memory web API in the [HTTP client chapter](../guide/server-communication.html#in-mem-web-api).
Remember, the in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes.
Skip it when you have a real web API server.
Read more about the in-memory web API in the
[Appendix: Tour of Heroes in-memory web api](../guide/server-communication.html#in-mem-web-api)
section of the [HTTP Client](../guide/server-communication.html#in-mem-web-api) page.
.l-main-section
:marked
## Heroes and HTTP
Look at our current `HeroService` implementation
In the current `HeroService` implementation, a !{_Promise} resolved with mock heroes is returned.
+makeExcerpt('toh-4/ts/src/app/hero.service.ts (old getHeroes)', 'get-heroes')
:marked
We returned a !{_Promise} resolved with mock heroes.
It may have seemed like overkill at the time, but we were anticipating the
day when we fetched heroes with an HTTP client and we knew that would have to be an asynchronous operation.
This was implementated in anticipation of ultimately
fetching heroes with an HTTP client, which must be an asynchronous operation.
That day has arrived! Let's convert `getHeroes()` to use HTTP.
Now convert `getHeroes()` to use HTTP.
+makeExcerpt('src/app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes')
:marked
Our updated import statements are now:
Update the import statements as follows:
+makeExcerpt('src/app/hero.service.ts (updated imports)', 'imports')
- var _h3id = `http-${_promise}`
:marked
Refresh the browser, and the hero data should be successfully loaded from the
Refresh the browser. The hero data should successfully load from the
mock server.
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
We're still returning a !{_Promise} but we're creating it differently.
block get-heroes-details
:marked
The Angular `http.get` returns an RxJS `Observable`.
*Observables* are a powerful way to manage asynchronous data flows.
We'll learn about [Observables](#observables) later in this chapter.
You'll read about [Observables](#observables) later in this page.
For *now* we get back on familiar ground by immediately
converting that `Observable` to a `Promise` using the `toPromise` operator.
For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator.
+makeExcerpt('src/app/hero.service.ts', 'to-promise', '')
:marked
Unfortunately, the Angular `Observable` doesn't have a `toPromise` operator ...
not out of the box.
The Angular `Observable` is a bare-bones implementation.
The Angular `Observable` doesn't have a `toPromise` operator out of the box.
There are scores of operators like `toPromise` that extend `Observable` with useful capabilities.
If we want those capabilities, we have to add the operators ourselves.
There are many operators like `toPromise` that extend `Observable` with useful capabilities.
To use those capabilities, you have to add the operators themselves.
That's as easy as importing them from the RxJS library like this:
+makeExcerpt('src/app/hero.service.ts', 'rxjs', '')
@ -175,158 +171,153 @@ block get-heroes-details
:marked
### Extracting the data in the *then* callback
In the *promise*'s `then` callback we call the `json` method of the HTTP `Response` to extract the
In the *Promise*'s `then` callback, you call the `json` method of the HTTP `Response` to extract the
data within the response.
+makeExcerpt('src/app/hero.service.ts', 'to-data', '')
:marked
That response JSON has a single `data` property.
The `data` property holds the !{_array} of *heroes* that the caller really wants.
So we grab that !{_array} and return it as the resolved !{_Promise} value.
The response JSON has a single `data` property, which
holds the !{_array} of heroes that the caller wants.
So you grab that !{_array} and return it as the resolved !{_Promise} value.
.alert.is-important
:marked
Pay close attention to the shape of the data returned by the server.
This particular *in-memory web API* example happens to return an object with a `data` property.
Your API might return something else. Adjust the code to match *your web API*.
Note the shape of the data that the server returns.
This particular in-memory web API example returns an object with a `data` property.
Your API might return something else. Adjust the code to match your web API.
:marked
The caller is unaware of these machinations. It receives a !{_Promise} of *heroes* just as it did before.
It has no idea that we fetched the heroes from the (mock) server.
It knows nothing of the twists and turns required to convert the HTTP response into heroes.
Such is the beauty and purpose of delegating data access to a service like this `HeroService`.
The caller is unaware that you fetched the heroes from the (mock) server.
It receives a !{_Promise} of *heroes* just as it did before.
### Error Handling
At the end of `getHeroes()` we `catch` server failures and pass them to an error handler:
At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler.
+makeExcerpt('src/app/hero.service.ts', 'catch', '')
:marked
This is a critical step!
We must anticipate HTTP failures as they happen frequently for reasons beyond our control.
This is a critical step.
You must anticipate HTTP failures, as they happen frequently for reasons beyond your control.
+makeExcerpt('src/app/hero.service.ts', 'handleError', '')
- var rejected_promise = _docsFor == 'dart' ? 'propagated exception' : 'rejected promise';
:marked
In this demo service we log the error to the console; we would do better in real life.
In this demo service, you log the error to the console; in real life,
you would handle the error in code. For a demo, this works.
The code also includes an error to
the caller in a !{rejected_promise}, so that the caller can display a proper error message to the user.
We've also decided to return a user friendly form of the error to
the caller in a !{rejected_promise} so that the caller can display a proper error message to the user.
### Get hero by id
The `HeroDetailComponent` asks the `HeroService` to fetch a single hero to edit.
The `HeroService` currently fetches all heroes and then finds the desired hero
by filtering for the one with the matching `id`.
That's fine in a simulation. It's wasteful to ask a real server for _all_ heroes when we only want one.
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (e.g., `api/hero/11`).
Update the `HeroService.getHero` method to make a _get-by-id_ request,
applying what we just learned to write `getHeroes`:
+makeExcerpt('src/app/hero.service.ts', 'getHero', '')
When the `HeroDetailComponent` asks the `HeroService` to fetch a hero,
the `HeroService` currently fetches all heroes and
filters for the one with the matching `id`.
That's fine for a simulation, but it's wasteful to ask a real server for all heroes when you only want one.
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as `api/hero/11`).
Update the `HeroService.getHero` method to make a _get-by-id_ request:
+makeExcerpt('src/app/hero.service.ts', 'getHero', 'src/app/hero.service.ts')
:marked
It's almost the same as `getHeroes`.
The URL identifies _which_ hero the server should update by encoding the hero id into the URL
to match the `api/hero/:id` pattern.
This request is almost the same as `getHeroes`.
The hero id in the URL identifies which hero the server should update.
We also adjust to the fact that the `data` in the response is a single hero object rather than !{_an} !{_array}.
Also, the `data` in the response is a single hero object rather than !{_an} !{_array}.
### Unchanged _getHeroes_ API
Although we made significant *internal* changes to `getHeroes()` and `getHero()`,
the public signatures did not change.
We still return a !{_Promise} from both methods.
We won't have to update any of the components that call them.
Although you made significant internal changes to `getHeroes()` and `getHero()`,
the public signatures didn't change.
You still return a !{_Promise} from both methods.
You won't have to update any of the components that call them.
Our stakeholders are thrilled with the web API integration so far.
Now they want the ability to create and delete heroes.
Let's see first what happens when we try to update a hero's details.
Now it's time to add the ability to create and delete heroes.
.l-main-section
:marked
## Update hero details
## Updating hero details
We can edit a hero's name already in the hero detail view. Go ahead and try
it. As we type, the hero name is updated in the view heading.
But when we hit the `Back` button, the changes are lost!
Try editing a hero's name in the hero detail view.
As you type, the hero name is updated in the view heading.
But if you click the Back button, the changes are lost.
Updates weren't lost before. What changed?
When the app used a list of mock heroes, updates were applied directly to the
hero objects within the single, app-wide, shared list. Now that we are fetching data
from a server, if we want changes to persist, we'll need to write them back to
hero objects within the single, app-wide, shared list. Now that you're fetching data
from a server, if you want changes to persist, you must write them back to
the server.
### Save hero details
### Add the ability to save hero details
Let's ensure that edits to a hero's name aren't lost. Start by adding,
to the end of the hero detail template, a save button with a `click` event
binding that invokes a new component method named `save`:
At the end of the hero detail template, add a save button with a `click` event
binding that invokes a new component method named `save`.
+makeExcerpt('src/app/hero-detail.component.html', 'save')
:marked
The `save` method persists hero name changes using the hero service
`update` method and then navigates back to the previous view:
Add the following `save` method, which persists hero name changes using the hero service
`update` method and then navigates back to the previous view.
+makeExcerpt('src/app/hero-detail.component.ts', 'save')
:marked
### Hero service `update` method
### Add a hero service _update_ method
The overall structure of the `update` method is similar to that of
`getHeroes`, although we'll use an HTTP _put_ to persist changes
server-side:
`getHeroes`, but it uses an HTTP *put* to persist server-side changes.
+makeExcerpt('src/app/hero.service.ts', 'update')
:marked
We identify _which_ hero the server should update by encoding the hero id in
the URL. The put body is the JSON string encoding of the hero, obtained by
calling `!{_JSON_stringify}`. We identify the body content type
(`application/json`) in the request header.
To identify which hero the server should update, the hero id is encoded in
the URL. The `put` body is the JSON string encoding of the hero, obtained by
calling `!{_JSON_stringify}`. The body content type
(`application/json`) is identified in the request header.
Refresh the browser and give it a try. Changes to hero names should now persist.
Refresh the browser, change a hero name, save your change, and click the browser `Back` button. Changes should now persist.
.l-main-section
:marked
## Add a hero
## Add the ability to add heroes
To add a new hero we need to know the hero's name. Let's use an input
element for that, paired with an add button.
To add a hero, the app needs the hero's name. You can use an `input`
element paired with an add button.
Insert the following into the heroes component HTML, first thing after
Insert the following into the heroes component HTML, just after
the heading:
+makeExcerpt('src/app/heroes.component.html', 'add')
:marked
In response to a click event, we call the component's click handler and then
clear the input field so that it will be ready to use for another name.
In response to a click event, call the component's click handler and then
clear the input field so that it's ready for another name.
+makeExcerpt('src/app/heroes.component.ts', 'add')
:marked
When the given name is non-blank, the handler delegates creation of the
named hero to the hero service, and then adds the new hero to our !{_array}.
named hero to the hero service, and then adds the new hero to the !{_array}.
Finally, we implement the `create` method in the `HeroService` class.
Implement the `create` method in the `HeroService` class.
+makeExcerpt('src/app/hero.service.ts', 'create')
:marked
Refresh the browser and create some new heroes!
Refresh the browser and create some heroes.
.l-main-section
:marked
## Delete a hero
## Add the ability to delete a hero
Too many heroes?
Let's add a delete button to each hero in the heroes view.
Each hero in the heroes view should have a delete button.
Add this button element to the heroes component HTML, right after the hero
name in the repeated `<li>` tag:
Add the following button element to the heroes component HTML, after the hero
name in the repeated `<li>` tag.
+makeExcerpt('src/app/heroes.component.html', 'delete', '')
@ -336,30 +327,30 @@ block get-heroes-details
+makeExcerpt('src/app/heroes.component.html', 'li-element')
:marked
In addition to calling the component's `delete` method, the delete button
click handling code stops the propagation of the click event &mdash; we
don't want the `<li>` click handler to be triggered because that would
select the hero that we are going to delete!
In addition to calling the component's `delete` method, the delete-button
click-handling code stops the propagation of the click event&mdash;you
don't want the `<li>` click handler to be triggered because doing so would
select the hero that the user will delete.
The logic of the `delete` handler is a bit trickier:
+makeExcerpt('src/app/heroes.component.ts', 'delete')
:marked
Of course, we delegate hero deletion to the hero service, but the component
Of course you delegate hero deletion to the hero service, but the component
is still responsible for updating the display: it removes the deleted hero
from the !{_array} and resets the selected hero if necessary.
from the !{_array} and resets the selected hero, if necessary.
:marked
We want our delete button to be placed at the far right of the hero entry.
This extra CSS accomplishes that:
To place the delete button at the far right of the hero entry,
add this additional CSS:
+makeExcerpt('src/app/heroes.component.css', 'additions')
:marked
### Hero service `delete` method
The hero service's `delete` method uses the _delete_ HTTP method to remove the hero from the server:
Add the hero service's `delete` method, which uses the _delete_ HTTP method to remove the hero from the server:
+makeExcerpt('src/app/hero.service.ts', 'delete')
@ -374,38 +365,36 @@ block observables-section-intro
:marked
Each `Http` service method returns an `Observable` of HTTP `Response` objects.
Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
In this section we learn to return the `Observable` directly and discuss when and why that might be
a good thing to do.
The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
This section shows you how, when, and why to return the `Observable` directly.
### Background
An *observable* is a stream of events that we can process with array-like operators.
An *observable* is a stream of events that you can process with array-like operators.
Angular core has basic support for observables.
We developers augment that support with operators and extensions from the
Angular core has basic support for observables.
Developers augment that support with operators and extensions from the
<a href="http://reactivex.io/rxjs" target="_blank" title="RxJS">RxJS library</a>.
We'll see how shortly.
You'll see how shortly.
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
Recall that the `HeroService` chained the `toPromise` operator to the `Observable` result of `http.get`.
That operator converted the `Observable` into a `Promise` and you passed that promise back to the caller.
Converting to a promise is often a good choice. We typically ask `http.get` to fetch a single chunk of data.
When we receive the data, we're done.
A single result in the form of a promise is easy for the calling component to consume
and it helps that promises are widely understood by JavaScript programmers.
Converting to a promise is often a good choice. You typically ask `http.get` to fetch a single chunk of data.
When you receive the data, you're done.
The calling component can easily consume a single result in the form of a promise.
:marked
But requests aren't always "one and done". We may start one request,
then cancel it, and make a different request before the server has responded to the first request.
Such a _request-cancel-new-request_ sequence is difficult to implement with *!{_Promise}s*.
It's easy with *!{_Observable}s* as we'll see.
But requests aren't always done only once.
You may start one request,
cancel it, and make a different request before the server has responded to the first request.
A *request-cancel-new-request* sequence is difficult to implement with *!{_Promise}s*, but
easy with *!{_Observable}s*.
### Search-by-name
### Add the ability to search by name
You're going to add a *hero search* feature to the Tour of Heroes.
As the user types a name into a search box, you'll make repeated HTTP requests for heroes filtered by that name.
We're going to add a *hero search* feature to the Tour of Heroes.
As the user types a name into a search box, we'll make repeated HTTP requests for heroes filtered by that name.
We start by creating `HeroSearchService` that sends search queries to our server's web api.
Start by creating `HeroSearchService` that sends search queries to the server's web API.
+makeExample('src/app/hero-search.service.ts')
@ -413,44 +402,42 @@ block observables-section-intro
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
in the `HeroService`, although the URL now has a query string.
+ifDocsFor('ts')
:marked
A more important difference: we no longer call `toPromise`.
Instead we return the *observable* from the `http.get`,
after chaining it to another RxJS operator, <code>map</code>,
to extract heroes from the response data.
<span if-docs="ts">More importantly, you no longer call `toPromise`.
Instead you return the *observable* from the the `htttp.get`,
after chaining it to another RxJS operator, <code>map</code>,
to extract heroes from the response data.
RxJS operator chaining makes response processing easy and readable.
See the [discuss below about operators](#rxjs-imports).
RxJS operator chaining makes response processing easy and readable.
See the [discuss below about operators](#rxjs-imports).
:marked
### HeroSearchComponent
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
Create a `HeroSearchComponent` that calls the new `HeroSearchService`.
The component template is simple &mdash; just a text box and a list of matching search results.
The component template is simple&mdash;just a text box and a list of matching search results.
+makeExample('src/app/hero-search.component.html')
:marked
We'll also want to add styles for the new component.
Also, add styles for the new component.
+makeExample('src/app/hero-search.component.css')
:marked
As the user types in the search box, a *keyup* event binding calls the component's `search` method with the new search box value.
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
As expected, the `*ngFor` repeats hero objects from the component's `heroes` property.
But, as we'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until we flow it through the `async` pipe (`AsyncPipe`).
But as you'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until you route it through the `async` pipe (`AsyncPipe`).
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
Time to create the `HeroSearchComponent` class and metadata.
Create the `HeroSearchComponent` class and metadata.
+makeExample('src/app/hero-search.component.ts')
:marked
#### Search terms
Let's focus on the `!{_priv}searchTerms`:
Focus on the `!{_priv}searchTerms`:
+makeExcerpt('src/app/hero-search.component.ts', 'searchTerms', '')
@ -463,55 +450,51 @@ block search-criteria-intro
:marked
<a id="ngoninit"></a>
#### Initialize the _**heroes**_ property (_**ngOnInit**_)
#### Initialize the *heroes* property (*ngOnInit*)
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
We're going to turn the stream
You can turn the stream
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
+makeExcerpt('src/app/hero-search.component.ts', 'search', '')
:marked
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of HTTP requests.
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests,
taxing server resources and burning through the cellular network data plan.
block observable-transformers
:marked
Fortunately, we can chain `Observable` operators to the string `Observable` that reduce the request flow.
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`.
You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
before passing along the latest string. We'll never make requests more frequently than 300ms.
* `distinctUntilChanged` ensures that we only send a request if the filter text changed.
There's no point in repeating a request for the same search term.
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
before passing along the latest string. You'll never make requests more frequently than 300ms.
* `distinctUntilChanged` ensures that a request is sent only if the filter text changed.
* `switchMap` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`.
It cancels and discards previous search observables, returning only the latest search service observable.
.l-sub-section
:marked
The [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
(formerly known as "flatMapLatest") is very clever.
Every qualifying key event can trigger an `http` method call.
Even with a 300ms pause between requests, we could have multiple HTTP requests in flight
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)
(formerly known as "flatMapLatest"),
every qualifying key event can trigger an `http` method call.
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
and they may not return in the order sent.
`switchMap` preserves the original request order while returning
only the observable from the most recent `http` method call.
Results from prior calls are canceled and discarded.
We also short-circuit the `http` method call and return an observable containing an empty array
if the search text is empty.
If the search text is empty, the `http` method call is also short circuited
and an observable containing an empty array is returned.
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending HTTP request
until the service supports that feature, a topic for another day.
We are content for now to discard unwanted results.
Note that until the service supports that feature, _canceling_ the `HeroSearchService` observable
doesn't actually abort a pending HTTP request.
For now, unwanted results are discarded.
:marked
* `catch` intercepts a failed observable.
Our simple example prints the error to the console; a real life application should do better.
Then we return an observable containing an empty array to clear the search result.
The simple example prints the error to the console; a real life app would do better.
Then to clear the search result, you return an observable containing an empty array.
a#rxjs-imports
:marked
@ -520,48 +503,48 @@ block observable-transformers
Most RxJS operators are not included in Angular's base `Observable` implementation.
The base implementation includes only what Angular itself requires.
If we want more RxJS features, we have to extend `Observable` by *importing* the libraries in which they are defined.
Here are all the RxJS imports _this_ component needs:
When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined.
Here are all the RxJS imports that _this_ component needs:
+makeExample('src/app/hero-search.component.ts','rxjs-imports','src/app/hero-search.component.ts (rxjs imports)')(format='.')
:marked
The `import 'rxjs/add/...'` syntax may be unfamiliar.
It's missing the usual list of symbols between the braces: `{...}`.
We don't need the operator symbols themselves.
You don't need the operator symbols themselves.
In each case, the mere act of importing the library
loads and executes the library's script file which, in turn, adds the operator to the `Observable` class.
:marked
### Add the search component to the dashboard
We add the hero search HTML element to the bottom of the `DashboardComponent` template.
Add the hero search HTML element to the bottom of the `DashboardComponent` template.
+makeExample('src/app/dashboard.component.html')(format='.')
- var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
- var declFile = _docsFor == 'dart' ? 'src/app/dashboard.component.ts' : 'src/app/app.module.ts'
:marked
Finally, we import `HeroSearchComponent` from
Finally, import `HeroSearchComponent` from
<span ngio-ex>hero-search.component.ts</span>
and add it to the `!{_declarations}` !{_array}:
and add it to the `!{_declarations}` !{_array}.
+makeExcerpt(declFile, 'search')
:marked
Run the app again, go to the *Dashboard*, and enter some text in the search box.
At some point it might look like this.
Run the app again. In the Dashboard, enter some text in the search box.
If you enter characters that match any existing hero names, you'll see something like this.
figure.image-display
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
.l-main-section
:marked
## Application structure and code
## App structure and code
Review the sample source code in the <live-example></live-example> for this chapter.
Verify that we have the following structure:
Review the sample source code in the <live-example></live-example> for this page.
Verify that you have the following structure:
block filetree
.filetree
@ -603,15 +586,15 @@ block filetree
:marked
## Home Stretch
We are at the end of our journey for now, but we have accomplished a lot.
- We added the necessary dependencies to use HTTP in our application.
- We refactored `HeroService` to load heroes from a web API.
- We extended `HeroService` to support post, put and delete methods.
- We updated our components to allow adding, editing and deleting of heroes.
- We configured an in-memory web API.
- We learned how to use !{_Observable}s.
You're at the end of your journey, and you've accomplished a lot.
- You added the necessary dependencies to use HTTP in the app.
- You refactored `HeroService` to load heroes from a web API.
- You extended `HeroService` to support post, put, and delete methods.
- You updated the components to allow adding, editing, and deleting of heroes.
- You configured an in-memory web API.
- You learned how to use !{_Observable}s.
Here are the files we _added or changed_ in this chapter.
Here are the files you added or changed in this page.
block file-summary
+makeTabs(
@ -650,7 +633,7 @@ block file-summary
.l-sub-section
:marked
### Next Step
## Next step
Return to the [learning path](../guide/learning-angular.html#architecture) where
you can read about the concepts and practices you discovered in this tutorial.
Return to the [learning path](../guide/learning-angular.html#architecture), where
you can read more about the concepts and practices found in this tutorial.