docs: clarify toh (#28571)

PR Close #28571
This commit is contained in:
Kapunahele Wong 2019-01-31 14:25:30 -05:00 committed by Miško Hevery
parent cb848b9410
commit 8f084d7214
14 changed files with 456 additions and 567 deletions

View File

@ -1,8 +1,4 @@
/* HeroesComponent's private CSS styles */ /* HeroesComponent's private CSS styles */
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes { .heroes {
margin: 0 0 2em 0; margin: 0 0 2em 0;
list-style-type: none; list-style-type: none;
@ -19,18 +15,18 @@
height: 1.6em; height: 1.6em;
border-radius: 4px; border-radius: 4px;
} }
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover { .heroes li:hover {
color: #607D8B; color: #607D8B;
background-color: #DDD; background-color: #DDD;
left: .1em; left: .1em;
} }
.heroes .text { .heroes li.selected {
position: relative; background-color: #CFD8DC;
top: -3px; color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
} }
.heroes .badge { .heroes .badge {
display: inline-block; display: inline-block;

View File

@ -34,4 +34,7 @@ export class HeroesComponent implements OnInit {
this.selectedHero = hero; this.selectedHero = hero;
} }
// #enddocregion on-select // #enddocregion on-select
// #docregion component
} }
// #enddocregion component

View File

@ -1,8 +1,4 @@
/* HeroesComponent's private CSS styles */ /* HeroesComponent's private CSS styles */
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes { .heroes {
margin: 0 0 2em 0; margin: 0 0 2em 0;
list-style-type: none; list-style-type: none;
@ -19,18 +15,18 @@
height: 1.6em; height: 1.6em;
border-radius: 4px; border-radius: 4px;
} }
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover { .heroes li:hover {
color: #607D8B; color: #607D8B;
background-color: #DDD; background-color: #DDD;
left: .1em; left: .1em;
} }
.heroes .text { .heroes li.selected {
position: relative; background-color: #CFD8DC;
top: -3px; color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
} }
.heroes .badge { .heroes .badge {
display: inline-block; display: inline-block;

View File

@ -1,8 +1,4 @@
/* HeroesComponent's private CSS styles */ /* HeroesComponent's private CSS styles */
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes { .heroes {
margin: 0 0 2em 0; margin: 0 0 2em 0;
list-style-type: none; list-style-type: none;
@ -19,18 +15,18 @@
height: 1.6em; height: 1.6em;
border-radius: 4px; border-radius: 4px;
} }
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover { .heroes li:hover {
color: #607D8B; color: #607D8B;
background-color: #DDD; background-color: #DDD;
left: .1em; left: .1em;
} }
.heroes .text { .heroes li.selected {
position: relative; background-color: #CFD8DC;
top: -3px; color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
} }
.heroes .badge { .heroes .badge {
display: inline-block; display: inline-block;
@ -43,8 +39,6 @@
left: -1px; left: -1px;
top: -4px; top: -4px;
height: 1.8em; height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em; margin-right: .8em;
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
} }

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -7,9 +7,7 @@ import { RouterModule, Routes } from '@angular/router';
// #docregion import-dashboard // #docregion import-dashboard
import { DashboardComponent } from './dashboard/dashboard.component'; import { DashboardComponent } from './dashboard/dashboard.component';
// #enddocregion import-dashboard // #enddocregion import-dashboard
// #docregion heroes-route
import { HeroesComponent } from './heroes/heroes.component'; import { HeroesComponent } from './heroes/heroes.component';
// #enddocregion heroes-route
// #docregion import-herodetail // #docregion import-herodetail
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; import { HeroDetailComponent } from './hero-detail/hero-detail.component';
// #enddocregion import-herodetail // #enddocregion import-herodetail
@ -39,7 +37,9 @@ const routes: Routes = [
imports: [ RouterModule.forRoot(routes) ], imports: [ RouterModule.forRoot(routes) ],
// #enddocregion ngmodule-imports // #enddocregion ngmodule-imports
// #docregion v1 // #docregion v1
// #docregion export-routermodule
exports: [ RouterModule ] exports: [ RouterModule ]
// #enddocregion export-routermodule
}) })
export class AppRoutingModule {} export class AppRoutingModule {}
// #enddocregion , v1 // #enddocregion , v1

View File

@ -23,13 +23,17 @@ import { HeroSearchComponent } from './hero-search/hero-search.component';
// #docregion v1 // #docregion v1
import { MessagesComponent } from './messages/messages.component'; import { MessagesComponent } from './messages/messages.component';
// #docregion import-httpclientmodule
@NgModule({ @NgModule({
imports: [ imports: [
// #enddocregion import-httpclientmodule
BrowserModule, BrowserModule,
FormsModule, FormsModule,
AppRoutingModule, AppRoutingModule,
// #docregion in-mem-web-api-imports // #docregion in-mem-web-api-imports
// #docregion import-httpclientmodule
HttpClientModule, HttpClientModule,
// #enddocregion import-httpclientmodule
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests // The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses. // and returns simulated server responses.
@ -38,7 +42,9 @@ import { MessagesComponent } from './messages/messages.component';
InMemoryDataService, { dataEncapsulation: false } InMemoryDataService, { dataEncapsulation: false }
) )
// #enddocregion in-mem-web-api-imports // #enddocregion in-mem-web-api-imports
// #docregion import-httpclientmodule
], ],
// #enddocregion import-httpclientmodule
declarations: [ declarations: [
AppComponent, AppComponent,
DashboardComponent, DashboardComponent,
@ -50,6 +56,9 @@ import { MessagesComponent } from './messages/messages.component';
// #docregion v1 // #docregion v1
], ],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
// #docregion import-httpclientmodule
}) })
// #enddocregion import-httpclientmodule
export class AppModule { } export class AppModule { }
// #enddocregion , v1 // #enddocregion , v1

View File

@ -13,11 +13,6 @@ import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from './hero'; import { Hero } from './hero';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
// #docregion http-options
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
// #enddocregion http-options
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class HeroService { export class HeroService {
@ -26,6 +21,12 @@ export class HeroService {
private heroesUrl = 'api/heroes'; // URL to web api private heroesUrl = 'api/heroes'; // URL to web api
// #enddocregion heroesUrl // #enddocregion heroesUrl
// #docregion http-options
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
// #enddocregion http-options
// #docregion ctor // #docregion ctor
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@ -96,7 +97,7 @@ export class HeroService {
// #docregion addHero // #docregion addHero
/** POST: add a new hero to the server */ /** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> { addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe( return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)), tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
catchError(this.handleError<Hero>('addHero')) catchError(this.handleError<Hero>('addHero'))
); );
@ -109,7 +110,7 @@ export class HeroService {
const id = typeof hero === 'number' ? hero : hero.id; const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`; const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, httpOptions).pipe( return this.http.delete<Hero>(url, this.httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)), tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero')) catchError(this.handleError<Hero>('deleteHero'))
); );
@ -119,7 +120,7 @@ export class HeroService {
// #docregion updateHero // #docregion updateHero
/** PUT: update the hero on the server */ /** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> { updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe( return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)), tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero')) catchError(this.handleError<any>('updateHero'))
); );

View File

@ -13,13 +13,11 @@ Using the Angular CLI, generate a new component named `heroes`.
</code-example> </code-example>
The CLI creates a new folder, `src/app/heroes/`, and generates The CLI creates a new folder, `src/app/heroes/`, and generates
the four files of the `HeroesComponent`. the three files of the `HeroesComponent` along with a test file.
The `HeroesComponent` class file is as follows: The `HeroesComponent` class file is as follows:
<code-example <code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1" header="app/heroes/heroes.component.ts (initial version)" linenums="false">
path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1"
header="app/heroes/heroes.component.ts (initial version)" linenums="false">
</code-example> </code-example>
You always import the `Component` symbol from the Angular core library You always import the `Component` symbol from the Angular core library
@ -38,13 +36,13 @@ The CLI generated three metadata properties:
The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors), The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors),
`'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template. `'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template.
The `ngOnInit` is a [lifecycle hook](guide/lifecycle-hooks#oninit). The `ngOnInit()` is a [lifecycle hook](guide/lifecycle-hooks#oninit).
Angular calls `ngOnInit` shortly after creating a component. Angular calls `ngOnInit()` shortly after creating a component.
It's a good place to put initialization logic. It's a good place to put initialization logic.
Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`. Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`.
### Add a _hero_ property ### Add a `hero` property
Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm." Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm."
@ -60,7 +58,7 @@ replace it with a data binding to the new `hero` property.
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" header="heroes.component.html" region="show-hero-1" linenums="false"> <code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" header="heroes.component.html" region="show-hero-1" linenums="false">
</code-example> </code-example>
## Show the _HeroesComponent_ view ## Show the `HeroesComponent` view
To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`. To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`.
@ -102,10 +100,7 @@ The page no longer displays properly because you changed the hero from a string
Update the binding in the template to announce the hero's name Update the binding in the template to announce the hero's name
and show both `id` and `name` in a details layout like this: and show both `id` and `name` in a details layout like this:
<code-example <code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" region="show-hero-2" header="heroes.component.html (HeroesComponent's template)" linenums="false">
path="toh-pt1/src/app/heroes/heroes.component.1.html"
region="show-hero-2"
header="heroes.component.html (HeroesComponent's template)" linenums="false">
</code-example> </code-example>
The browser refreshes and displays the hero's information. The browser refreshes and displays the hero's information.
@ -113,9 +108,7 @@ The browser refreshes and displays the hero's information.
## Format with the _UppercasePipe_ ## Format with the _UppercasePipe_
Modify the `hero.name` binding like this. Modify the `hero.name` binding like this.
<code-example <code-example path="toh-pt1/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="pipe">
path="toh-pt1/src/app/heroes/heroes.component.html"
region="pipe">
</code-example> </code-example>
The browser refreshes and now the hero's name is displayed in capital letters. The browser refreshes and now the hero's name is displayed in capital letters.
@ -133,7 +126,7 @@ Users should be able to edit the hero name in an `<input>` textbox.
The textbox should both _display_ the hero's `name` property The textbox should both _display_ the hero's `name` property
and _update_ that property as the user types. and _update_ that property as the user types.
That means data flow from the component class _out to the screen_ and That means data flows from the component class _out to the screen_ and
from the screen _back to the class_. from the screen _back to the class_.
To automate that data flow, setup a two-way data binding between the `<input>` form element and the `hero.name` property. To automate that data flow, setup a two-way data binding between the `<input>` form element and the `hero.name` property.
@ -170,7 +163,7 @@ It belongs to the optional `FormsModule` and you must _opt-in_ to using it.
Angular needs to know how the pieces of your application fit together Angular needs to know how the pieces of your application fit together
and what other files and libraries the app requires. and what other files and libraries the app requires.
This information is called _metadata_ This information is called _metadata_.
Some of the metadata is in the `@Component` decorators that you added to your component classes. Some of the metadata is in the `@Component` decorators that you added to your component classes.
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators. Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
@ -196,7 +189,7 @@ region="ng-imports">
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. 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.
### Declare _HeroesComponent_ ### Declare `HeroesComponent`
Every component must be declared in _exactly one_ [NgModule](guide/ngmodules). Every component must be declared in _exactly one_ [NgModule](guide/ngmodules).
@ -206,11 +199,11 @@ So why did the application work?
It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component. It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component.
Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top. Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top.
<code-example path="toh-pt1/src/app/app.module.ts" region="heroes-import" > <code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="heroes-import" >
</code-example> </code-example>
The `HeroesComponent` is declared in the `@NgModule.declarations` array. The `HeroesComponent` is declared in the `@NgModule.declarations` array.
<code-example path="toh-pt1/src/app/app.module.ts" region="declarations"> <code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="declarations">
</code-example> </code-example>
Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`. Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`.

View File

@ -21,19 +21,17 @@ header="src/app/mock-heroes.ts">
## Displaying heroes ## Displaying heroes
You're about to display the list of heroes at the top of the `HeroesComponent`.
Open the `HeroesComponent` class file and import the mock `HEROES`. Open the `HeroesComponent` class file and import the mock `HEROES`.
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="import-heroes" header="src/app/heroes/heroes.component.ts (import HEROES)"> <code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="import-heroes" header="src/app/heroes/heroes.component.ts (import HEROES)">
</code-example> </code-example>
In the same file (`HeroesComponent` class), define a component property called `heroes` to expose `HEROES` array for binding. In the same file (`HeroesComponent` class), define a component property called `heroes` to expose the `HEROES` array for binding.
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="component"> <code-example path="toh-pt2/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="component">
</code-example> </code-example>
### List heroes with _*ngFor_ ### List heroes with `*ngFor`
Open the `HeroesComponent` template file and make the following changes: Open the `HeroesComponent` template file and make the following changes:
@ -47,7 +45,7 @@ Make it look like this:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="list" header="heroes.component.html (heroes template)" linenums="false"> <code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="list" header="heroes.component.html (heroes template)" linenums="false">
</code-example> </code-example>
Now change the `<li>` to this: That shows one hero. To list them all, add an `*ngFor` to the `<li>` to iterate through the list of heroes:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="li"> <code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="li">
</code-example> </code-example>
@ -55,10 +53,10 @@ Now change the `<li>` to this:
The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive. The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive.
It repeats the host element for each element in a list. It repeats the host element for each element in a list.
In this example The syntax in this example is as follows:
* `<li>` is the host element * `<li>` is the host element.
* `heroes` is the list from the `HeroesComponent` class. * `heroes` holds the mock heroes list from the `HeroesComponent` class, the mock heroes list.
* `hero` holds the current hero object for each iteration through the list. * `hero` holds the current hero object for each iteration through the list.
<div class="alert is-important"> <div class="alert is-important">
@ -127,9 +125,10 @@ This is an example of Angular's [event binding](guide/template-syntax#event-bind
The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event. The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event.
When the user clicks in the `<li>`, Angular executes the `onSelect(hero)` expression. When the user clicks in the `<li>`, Angular executes the `onSelect(hero)` expression.
`onSelect()` is a `HeroesComponent` method that you're about to write.
Angular calls it with the `hero` object displayed in the clicked `<li>`, In the next section, define an `onSelect()` method in `HeroesComponent` to
the same `hero` defined previously in the `*ngFor` expression. display the hero that was defined in the `*ngFor` expression.
### Add the click event handler ### Add the click event handler
@ -142,10 +141,11 @@ to the component's `selectedHero`.
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="on-select" header="src/app/heroes/heroes.component.ts (onSelect)" linenums="false"> <code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="on-select" header="src/app/heroes/heroes.component.ts (onSelect)" linenums="false">
</code-example> </code-example>
### Update the details template ### Add a details section
The template still refers to the component's old `hero` property which no longer exists. Currently, you have a list in the component template. To click on a hero on the list
Rename `hero` to `selectedHero`. and reveal details about that hero, you need a section for the details to render in the
template. Add the following to `heroes.component.html` beneath the list section:
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="selectedHero-details" header="heroes.component.html (selected hero details)" linenums="false"> <code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="selectedHero-details" header="heroes.component.html (selected hero details)" linenums="false">
</code-example> </code-example>
@ -192,7 +192,7 @@ The heroes appear in a list and details about the clicked hero appear at the bot
#### Why it works #### Why it works
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to worry about. When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to consider.
When the user picks a hero, `selectedHero` has a value and When the user picks a hero, `selectedHero` has a value and
`ngIf` puts the hero detail into the DOM. `ngIf` puts the hero detail into the DOM.

View File

@ -72,7 +72,7 @@ Amend the `@angular/core` import statement to include the `Input` symbol.
Add a `hero` property, preceded by the `@Input()` decorator. Add a `hero` property, preceded by the `@Input()` decorator.
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" region="input-hero" linenums="false"> <code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" header="src/app/hero-detail/hero-detail.component.ts" region="input-hero" linenums="false">
</code-example> </code-example>
That's the only change you should make to the `HeroDetailComponent` class. That's the only change you should make to the `HeroDetailComponent` class.

View File

@ -18,11 +18,11 @@ to inject it into the `HeroesComponent` constructor.
Services are a great way to share information among classes that _don't know each other_. Services are a great way to share information among classes that _don't know each other_.
You'll create a `MessageService` and inject it in two places: You'll create a `MessageService` and inject it in two places:
1. in `HeroService` which uses the service to send a message. 1. in `HeroService` which uses the service to send a message
2. in `MessagesComponent` which displays that message. 2. in `MessagesComponent` which displays that message
## Create the _HeroService_ ## Create the `HeroService`
Using the Angular CLI, create a service called `hero`. Using the Angular CLI, create a service called `hero`.
@ -30,14 +30,14 @@ Using the Angular CLI, create a service called `hero`.
ng generate service hero ng generate service hero
</code-example> </code-example>
The command generates skeleton `HeroService` class in `src/app/hero.service.ts` The command generates a skeleton `HeroService` class in `src/app/hero.service.ts` as follows:
The `HeroService` class should look like the following example.
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new" <code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
header="src/app/hero.service.ts (new service)" linenums="false"> header="src/app/hero.service.ts (new service)" linenums="false">
</code-example> </code-example>
### _@Injectable()_ services
### `@Injectable()` services
Notice that the new service imports the Angular `Injectable` symbol and annotates Notice that the new service imports the Angular `Injectable` symbol and annotates
the class with the `@Injectable()` decorator. This marks the class as one that participates in the _dependency injection system_. The `HeroService` class is going to provide an injectable service, and it can also have its own injected dependencies. the class with the `@Injectable()` decorator. This marks the class as one that participates in the _dependency injection system_. The `HeroService` class is going to provide an injectable service, and it can also have its own injected dependencies.
@ -56,27 +56,25 @@ The implementation in _this_ tutorial will continue to deliver _mock heroes_.
Import the `Hero` and `HEROES`. Import the `Hero` and `HEROES`.
<code-example path="toh-pt4/src/app/hero.service.ts" region="import-heroes"> <code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="import-heroes">
</code-example> </code-example>
Add a `getHeroes` method to return the _mock heroes_. Add a `getHeroes` method to return the _mock heroes_.
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="getHeroes"> <code-example path="toh-pt4/src/app/hero.service.1.ts" header="src/app/hero.service.ts" region="getHeroes">
</code-example> </code-example>
{@a provide} {@a provide}
## Provide the `HeroService` ## Provide the `HeroService`
You must make the `HeroService` available to the dependency injection system You must make the `HeroService` available to the dependency injection system
before Angular can _inject_ it into the `HeroesComponent`, before Angular can _inject_ it into the `HeroesComponent` by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
as you will do [below](#inject). You do this by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
Now, you need to make sure that the `HeroService` is registered as the provider of this service. To make sure that the `HeroService` can provide this service, register it
You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required. with the _injector_, which is the object that is responsible for choosing
and injecting the provider where the app requires it.
By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata in the `@Injectable` decorator. By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata, that is `providedIn: 'root'` in the `@Injectable()` decorator.
If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root':
``` ```
@Injectable({ @Injectable({
@ -115,7 +113,7 @@ Import the `HeroService` instead.
Replace the definition of the `heroes` property with a simple declaration. Replace the definition of the `heroes` property with a simple declaration.
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="heroes"> <code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="heroes">
</code-example> </code-example>
{@a inject} {@a inject}
@ -124,7 +122,7 @@ Replace the definition of the `heroes` property with a simple declaration.
Add a private `heroService` parameter of type `HeroService` to the constructor. Add a private `heroService` parameter of type `HeroService` to the constructor.
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ctor"> <code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="ctor">
</code-example> </code-example>
The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site. The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site.
@ -132,16 +130,16 @@ The parameter simultaneously defines a private `heroService` property and identi
When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system
sets the `heroService` parameter to the singleton instance of `HeroService`. sets the `heroService` parameter to the singleton instance of `HeroService`.
### Add _getHeroes()_ ### Add `getHeroes()`
Create a function to retrieve the heroes from the service. Create a function to retrieve the heroes from the service.
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes"> <code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" region="getHeroes">
</code-example> </code-example>
{@a oninit} {@a oninit}
### Call it in `ngOnInit` ### Call it in `ngOnInit()`
While you could call `getHeroes()` in the constructor, that's not the best practice. While you could call `getHeroes()` in the constructor, that's not the best practice.
@ -150,9 +148,9 @@ The constructor shouldn't _do anything_.
It certainly shouldn't call a function that makes HTTP requests to a remote server as a _real_ data service would. It certainly shouldn't call a function that makes HTTP requests to a remote server as a _real_ data service would.
Instead, call `getHeroes()` inside the [*ngOnInit lifecycle hook*](guide/lifecycle-hooks) and Instead, call `getHeroes()` inside the [*ngOnInit lifecycle hook*](guide/lifecycle-hooks) and
let Angular call `ngOnInit` at an appropriate time _after_ constructing a `HeroesComponent` instance. let Angular call `ngOnInit()` at an appropriate time _after_ constructing a `HeroesComponent` instance.
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ng-on-init"> <code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="ng-on-init">
</code-example> </code-example>
### See it run ### See it run
@ -167,7 +165,7 @@ which implies that the `HeroService` can fetch heroes synchronously.
The `HeroesComponent` consumes the `getHeroes()` result The `HeroesComponent` consumes the `getHeroes()` result
as if heroes could be fetched synchronously. as if heroes could be fetched synchronously.
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="get-heroes"> <code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" region="get-heroes">
</code-example> </code-example>
This will not work in a real app. This will not work in a real app.
@ -181,13 +179,11 @@ and the browser will not block while the service waits.
`HeroService.getHeroes()` must have an _asynchronous signature_ of some kind. `HeroService.getHeroes()` must have an _asynchronous signature_ of some kind.
It can take a callback. It could return a `Promise`. It could return an `Observable`.
In this tutorial, `HeroService.getHeroes()` will return an `Observable` In this tutorial, `HeroService.getHeroes()` will return an `Observable`
in part because it will eventually use the Angular `HttpClient.get` method to fetch the heroes because it will eventually use the Angular `HttpClient.get` method to fetch the heroes
and [`HttpClient.get()` returns an `Observable`](guide/http). and [`HttpClient.get()` returns an `Observable`](guide/http).
### Observable _HeroService_ ### Observable `HeroService`
`Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/). `Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/).
@ -196,13 +192,12 @@ In this tutorial, you'll simulate getting data from the server with the RxJS `of
Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS. Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS.
<code-example path="toh-pt4/src/app/hero.service.ts" <code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts (Observable imports)" region="import-observable">
header="src/app/hero.service.ts (Observable imports)" region="import-observable">
</code-example> </code-example>
Replace the `getHeroes` method with this one. Replace the `getHeroes()` method with the following:
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1"></code-example> <code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1"></code-example>
`of(HEROES)` returns an `Observable<Hero[]>` that emits _a single value_, the array of mock heroes. `of(HEROES)` returns an `Observable<Hero[]>` that emits _a single value_, the array of mock heroes.
@ -212,7 +207,7 @@ In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get<Hero[]>()`
</div> </div>
### Subscribe in _HeroesComponent_ ### Subscribe in `HeroesComponent`
The `HeroService.getHeroes` method used to return a `Hero[]`. The `HeroService.getHeroes` method used to return a `Hero[]`.
Now it returns an `Observable<Hero[]>`. Now it returns an `Observable<Hero[]>`.
@ -242,9 +237,9 @@ or the browser could freeze the UI while it waited for the server's response.
That _won't work_ when the `HeroService` is actually making requests of a remote server. That _won't work_ when the `HeroService` is actually making requests of a remote server.
The new version waits for the `Observable` to emit the array of heroes&mdash; The new version waits for the `Observable` to emit the array of heroes&mdash;which
which could happen now or several minutes from now. could happen now or several minutes from now.
Then `subscribe` passes the emitted array to the callback, The `subscribe()` method passes the emitted array to the callback,
which sets the component's `heroes` property. which sets the component's `heroes` property.
This asynchronous approach _will work_ when This asynchronous approach _will work_ when
@ -252,14 +247,14 @@ the `HeroService` requests heroes from the server.
## Show messages ## Show messages
In this section you will This section guides you through the following:
* add a `MessagesComponent` that displays app messages at the bottom of the screen. * adding a `MessagesComponent` that displays app messages at the bottom of the screen
* create an injectable, app-wide `MessageService` for sending messages to be displayed * creating an injectable, app-wide `MessageService` for sending messages to be displayed
* inject `MessageService` into the `HeroService` * injecting `MessageService` into the `HeroService`
* display a message when `HeroService` fetches heroes successfully. * displaying a message when `HeroService` fetches heroes successfully
### Create _MessagesComponent_ ### Create `MessagesComponent`
Use the CLI to create the `MessagesComponent`. Use the CLI to create the `MessagesComponent`.
@ -269,16 +264,16 @@ Use the CLI to create the `MessagesComponent`.
The CLI creates the component files in the `src/app/messages` folder and declares the `MessagesComponent` in `AppModule`. The CLI creates the component files in the `src/app/messages` folder and declares the `MessagesComponent` in `AppModule`.
Modify the `AppComponent` template to display the generated `MessagesComponent` Modify the `AppComponent` template to display the generated `MessagesComponent`.
<code-example <code-example
header = "/src/app/app.component.html" header = "src/app/app.component.html"
path="toh-pt4/src/app/app.component.html"> path="toh-pt4/src/app/app.component.html">
</code-example> </code-example>
You should see the default paragraph from `MessagesComponent` at the bottom of the page. You should see the default paragraph from `MessagesComponent` at the bottom of the page.
### Create the _MessageService_ ### Create the `MessageService`
Use the CLI to create the `MessageService` in `src/app`. Use the CLI to create the `MessageService` in `src/app`.
@ -288,9 +283,7 @@ Use the CLI to create the `MessageService` in `src/app`.
Open `MessageService` and replace its contents with the following. Open `MessageService` and replace its contents with the following.
<code-example <code-example header = "src/app/message.service.ts" path="toh-pt4/src/app/message.service.ts">
header = "/src/app/message.service.ts"
path="toh-pt4/src/app/message.service.ts">
</code-example> </code-example>
The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache. The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache.
@ -298,10 +291,10 @@ The service exposes its cache of `messages` and two methods: one to `add()` a me
{@a inject-message-service} {@a inject-message-service}
### Inject it into the `HeroService` ### Inject it into the `HeroService`
Re-open the `HeroService` and import the `MessageService`. In `HeroService`, import the `MessageService`.
<code-example <code-example
header = "/src/app/hero.service.ts (import MessageService)" header = "src/app/hero.service.ts (import MessageService)"
path="toh-pt4/src/app/hero.service.ts" region="import-message-service"> path="toh-pt4/src/app/hero.service.ts" region="import-message-service">
</code-example> </code-example>
@ -310,7 +303,7 @@ Angular will inject the singleton `MessageService` into that property
when it creates the `HeroService`. when it creates the `HeroService`.
<code-example <code-example
path="toh-pt4/src/app/hero.service.ts" region="ctor"> path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
@ -322,9 +315,9 @@ you inject the `MessageService` into the `HeroService` which is injected into th
### Send a message from `HeroService` ### Send a message from `HeroService`
Modify the `getHeroes` method to send a message when the heroes are fetched. Modify the `getHeroes()` method to send a message when the heroes are fetched.
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes"> <code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes">
</code-example> </code-example>
### Display the message from `HeroService` ### Display the message from `HeroService`
@ -334,20 +327,17 @@ including the message sent by the `HeroService` when it fetches heroes.
Open `MessagesComponent` and import the `MessageService`. Open `MessagesComponent` and import the `MessageService`.
<code-example <code-example header="src/app/messages/messages.component.ts (import MessageService)" path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
header = "/src/app/messages/messages.component.ts (import MessageService)"
path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
</code-example> </code-example>
Modify the constructor with a parameter that declares a **public** `messageService` property. Modify the constructor with a parameter that declares a **public** `messageService` property.
Angular will inject the singleton `MessageService` into that property Angular will inject the singleton `MessageService` into that property
when it creates the `MessagesComponent`. when it creates the `MessagesComponent`.
<code-example <code-example path="toh-pt4/src/app/messages/messages.component.ts" header="src/app/messages/messages.component.ts" region="ctor">
path="toh-pt4/src/app/messages/messages.component.ts" region="ctor">
</code-example> </code-example>
The `messageService` property **must be public** because you're about to bind to it in the template. The `messageService` property **must be public** because you're going to bind to it in the template.
<div class="alert is-important"> <div class="alert is-important">
@ -355,7 +345,7 @@ Angular only binds to _public_ component properties.
</div> </div>
### Bind to the _MessageService_ ### Bind to the `MessageService`
Replace the CLI-generated `MessagesComponent` template with the following. Replace the CLI-generated `MessagesComponent` template with the following.

View File

@ -36,59 +36,49 @@ Use the CLI to generate it.
The generated file looks like this: The generated file looks like this:
<code-example path="toh-pt5/src/app/app-routing.module.0.ts" <code-example path="toh-pt5/src/app/app-routing.module.0.ts" header="src/app/app-routing.module.ts (generated)">
header="src/app/app-routing.module.ts (generated)">
</code-example> </code-example>
You generally don't declare components in a routing module so you can delete the Replace it with the following:
`@NgModule.declarations` array and delete `CommonModule` references too.
You'll configure the router with `Routes` in the `RouterModule` <code-example path="toh-pt5/src/app/app-routing.module.1.ts" header="src/app/app-routing.module.ts (updated)">
so import those two symbols from the `@angular/router` library.
Add an `@NgModule.exports` array with `RouterModule` in it.
Exporting `RouterModule` makes router directives available for use
in the `AppModule` components that will need them.
`AppRoutingModule` looks like this now:
<code-example path="toh-pt5/src/app/app-routing.module.ts"
region="v1"
header="src/app/app-routing.module.ts (v1)">
</code-example> </code-example>
### Add routes First, `AppRoutingModule` imports `RouterModule` and `Routes` so the app can have routing functionality. The next import, `HeroesComponent`, will give the Router somewhere to go once you configure the routes.
*Routes* tell the router which view to display when a user clicks a link or Notice that the `CommonModule` references and `declarations` array are unecessary, so are no
longer part of `AppRoutingModule`. The following sections explain the rest of the `AppRoutingModule` in more detail.
### Routes
The next part of the file is where you configure your routes.
*Routes* tell the Router which view to display when a user clicks a link or
pastes a URL into the browser address bar. pastes a URL into the browser address bar.
A typical Angular `Route` has two properties: Since `AppRoutingModule` already imports `HeroesComponent`, you can use it in the `routes` array:
1. `path`: a string that matches the URL in the browser address bar. <code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts"
1. `component`: the component that the router should create when navigating to this route.
You intend to navigate to the `HeroesComponent` when the URL is something like `localhost:4200/heroes`.
Import the `HeroesComponent` so you can reference it in a `Route`.
Then define an array of routes with a single `route` to that component.
<code-example path="toh-pt5/src/app/app-routing.module.ts"
region="heroes-route"> region="heroes-route">
</code-example> </code-example>
Once you've finished setting up, the router will match that URL to `path: 'heroes'` A typical Angular `Route` has two properties:
and display the `HeroesComponent`.
### _RouterModule.forRoot()_ * `path`: a string that matches the URL in the browser address bar.
* `component`: the component that the router should create when navigating to this route.
You first must initialize the router and start it listening for browser location changes. This tells the router to match that URL to `path: 'heroes'`
and display the `HeroesComponent` when the URL is something like `localhost:4200/heroes`.
Add `RouterModule` to the `@NgModule.imports` array and ### `RouterModule.forRoot()`
configure it with the `routes` in one step by calling
`RouterModule.forRoot()` _within_ the `imports` array, like this:
<code-example path="toh-pt5/src/app/app-routing.module.ts" The `@NgModule` metadata initializes the router and starts it listening for browser location changes.
region="ngmodule-imports">
The following line adds the `RouterModule` to the `AppRoutingModule` `imports` array and
configures it with the `routes` in one step by calling
`RouterModule.forRoot()`:
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="ngmodule-imports">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
@ -99,16 +89,21 @@ configure it with the `routes` in one step by calling
</div> </div>
## Add _RouterOutlet_ Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element. Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
<code-example path="toh-pt5/src/app/app.component.html" <code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
region="outlet"
header="src/app/app.component.html (router-outlet)">
</code-example> </code-example>
You removed `<app-heroes>` because you will only display the `HeroesComponent` when the user navigates to it. ## Add `RouterOutlet`
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
<code-example path="toh-pt5/src/app/app.component.html" region="outlet" header="src/app/app.component.html (router-outlet)">
</code-example>
The `AppComponent` template no longer needs `<app-heroes>` because the app will only display the `HeroesComponent` when the user navigates to it.
The `<router-outlet>` tells the router where to display routed views. The `<router-outlet>` tells the router where to display routed views.
@ -140,22 +135,19 @@ You should see the familiar heroes master/detail view.
## Add a navigation link (`routerLink`) ## Add a navigation link (`routerLink`)
Users shouldn't have to paste a route URL into the address bar. Ideally, users should be able to click a link to navigate rather
They should be able to click a link to navigate. than pasting a route URL into the address bar.
Add a `<nav>` element and, within that, an anchor element that, when clicked, Add a `<nav>` element and, within that, an anchor element that, when clicked,
triggers navigation to the `HeroesComponent`. triggers navigation to the `HeroesComponent`.
The revised `AppComponent` template looks like this: The revised `AppComponent` template looks like this:
<code-example <code-example path="toh-pt5/src/app/app.component.html" region="heroes" header="src/app/app.component.html (heroes RouterLink)">
path="toh-pt5/src/app/app.component.html"
region="heroes"
header="src/app/app.component.html (heroes RouterLink)">
</code-example> </code-example>
A [`routerLink` attribute](#routerlink) is set to `"/heroes"`, A [`routerLink` attribute](#routerlink) is set to `"/heroes"`,
the string that the router matches to the route to `HeroesComponent`. the string that the router matches to the route to `HeroesComponent`.
The `routerLink` is the selector for the [`RouterLink` directive](#routerlink) The `routerLink` is the selector for the [`RouterLink` directive](/api/router/RouterLink)
that turns user clicks into router navigations. that turns user clicks into router navigations.
It's another of the public directives in the `RouterModule`. It's another of the public directives in the `RouterModule`.
@ -186,7 +178,7 @@ Add a `DashboardComponent` using the CLI:
The CLI generates the files for the `DashboardComponent` and declares it in `AppModule`. The CLI generates the files for the `DashboardComponent` and declares it in `AppModule`.
Replace the default file content in these three files as follows and then return for a little discussion: Replace the default file content in these three files as follows:
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -211,11 +203,11 @@ The _template_ presents a grid of hero name links.
The _class_ is similar to the `HeroesComponent` class. The _class_ is similar to the `HeroesComponent` class.
* It defines a `heroes` array property. * It defines a `heroes` array property.
* The constructor expects Angular to inject the `HeroService` into a private `heroService` property. * The constructor expects Angular to inject the `HeroService` into a private `heroService` property.
* The `ngOnInit()` lifecycle hook calls `getHeroes`. * The `ngOnInit()` lifecycle hook calls `getHeroes()`.
This `getHeroes` returns the sliced list of heroes at positions 1 and 5, returning only four of the Top Heroes (2nd, 3rd, 4th, and 5th). This `getHeroes()` returns the sliced list of heroes at positions 1 and 5, returning only four of the Top Heroes (2nd, 3rd, 4th, and 5th).
<code-example path="toh-pt5/src/app/dashboard/dashboard.component.ts" region="getHeroes"> <code-example path="toh-pt5/src/app/dashboard/dashboard.component.ts" header="src/app/dashboard/dashboard.component.ts" region="getHeroes">
</code-example> </code-example>
### Add the dashboard route ### Add the dashboard route
@ -224,29 +216,24 @@ To navigate to the dashboard, the router needs an appropriate route.
Import the `DashboardComponent` in the `AppRoutingModule`. Import the `DashboardComponent` in the `AppRoutingModule`.
<code-example <code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-dashboard" header="src/app/app-routing.module.ts (import DashboardComponent)">
path="toh-pt5/src/app/app-routing.module.ts"
region="import-dashboard"
header="src/app/app-routing.module.ts (import DashboardComponent)">
</code-example> </code-example>
Add a route to the `AppRoutingModule.routes` array that matches a path to the `DashboardComponent`. Add a route to the `AppRoutingModule.routes` array that matches a path to the `DashboardComponent`.
<code-example <code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="dashboard-route">
path="toh-pt5/src/app/app-routing.module.ts"
region="dashboard-route">
</code-example> </code-example>
### Add a default route ### Add a default route
When the app starts, the browsers address bar points to the web site's root. When the app starts, the browser's address bar points to the web site's root.
That doesn't match any existing route so the router doesn't navigate anywhere. That doesn't match any existing route so the router doesn't navigate anywhere.
The space below the `<router-outlet>` is blank. The space below the `<router-outlet>` is blank.
To make the app navigate to the dashboard automatically, add the following To make the app navigate to the dashboard automatically, add the following
route to the `AppRoutingModule.Routes` array. route to the `AppRoutingModule.Routes` array.
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="redirect-route"> <code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="redirect-route">
</code-example> </code-example>
This route redirects a URL that fully matches the empty path to the route whose path is `'/dashboard'`. This route redirects a URL that fully matches the empty path to the route whose path is `'/dashboard'`.
@ -301,27 +288,19 @@ A URL like `~/detail/11` would be a good URL for navigating to the *Hero Detail*
Open `AppRoutingModule` and import `HeroDetailComponent`. Open `AppRoutingModule` and import `HeroDetailComponent`.
<code-example <code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-herodetail" header="src/app/app-routing.module.ts (import HeroDetailComponent)">
path="toh-pt5/src/app/app-routing.module.ts"
region="import-herodetail"
header="src/app/app-routing.module.ts (import HeroDetailComponent)">
</code-example> </code-example>
Then add a _parameterized_ route to the `AppRoutingModule.routes` array that matches the path pattern to the _hero detail_ view. Then add a _parameterized_ route to the `AppRoutingModule.routes` array that matches the path pattern to the _hero detail_ view.
<code-example <code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="detail-route">
path="toh-pt5/src/app/app-routing.module.ts"
region="detail-route">
</code-example> </code-example>
The colon (:) in the `path` indicates that `:id` is a placeholder for a specific hero `id`. The colon (:) in the `path` indicates that `:id` is a placeholder for a specific hero `id`.
At this point, all application routes are in place. At this point, all application routes are in place.
<code-example <code-example path="toh-pt5/src/app/app-routing.module.ts" region="routes" header="src/app/app-routing.module.ts (all routes)">
path="toh-pt5/src/app/app-routing.module.ts"
region="routes"
header="src/app/app-routing.module.ts (all routes)">
</code-example> </code-example>
### `DashboardComponent` hero links ### `DashboardComponent` hero links
@ -347,10 +326,7 @@ to insert the current iteration's `hero.id` into each
The hero items in the `HeroesComponent` are `<li>` elements whose click events The hero items in the `HeroesComponent` are `<li>` elements whose click events
are bound to the component's `onSelect()` method. are bound to the component's `onSelect()` method.
<code-example <code-example path="toh-pt4/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with onSelect)">
path="toh-pt4/src/app/heroes/heroes.component.html"
region="list"
header="src/app/heroes/heroes.component.html (list with onSelect)">
</code-example> </code-example>
Strip the `<li>` back to just its `*ngFor`, Strip the `<li>` back to just its `*ngFor`,
@ -358,10 +334,7 @@ wrap the badge and name in an anchor element (`<a>`),
and add a `routerLink` attribute to the anchor that and add a `routerLink` attribute to the anchor that
is the same as in the dashboard template is the same as in the dashboard template
<code-example <code-example path="toh-pt5/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with links)">
path="toh-pt5/src/app/heroes/heroes.component.html"
region="list"
header="src/app/heroes/heroes.component.html (list with links)">
</code-example> </code-example>
You'll have to fix the private stylesheet (`heroes.component.css`) to make You'll have to fix the private stylesheet (`heroes.component.css`) to make
@ -376,13 +349,10 @@ the `onSelect()` method and `selectedHero` property are no longer used.
It's nice to tidy up and you'll be grateful to yourself later. It's nice to tidy up and you'll be grateful to yourself later.
Here's the class after pruning away the dead code. Here's the class after pruning away the dead code.
<code-example <code-example path="toh-pt5/src/app/heroes/heroes.component.ts" region="class" header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
path="toh-pt5/src/app/heroes/heroes.component.ts"
region="class"
header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
</code-example> </code-example>
## Routable *HeroDetailComponent* ## Routable `HeroDetailComponent`
Previously, the parent `HeroesComponent` set the `HeroDetailComponent.hero` Previously, the parent `HeroesComponent` set the `HeroDetailComponent.hero`
property and the `HeroDetailComponent` displayed the hero. property and the `HeroDetailComponent` displayed the hero.
@ -390,18 +360,16 @@ property and the `HeroDetailComponent` displayed the hero.
`HeroesComponent` doesn't do that anymore. `HeroesComponent` doesn't do that anymore.
Now the router creates the `HeroDetailComponent` in response to a URL such as `~/detail/11`. Now the router creates the `HeroDetailComponent` in response to a URL such as `~/detail/11`.
The `HeroDetailComponent` needs a new way to obtain the _hero-to-display_. The `HeroDetailComponent` needs a new way to obtain the hero-to-display.
This section explains the following:
* Get the route that created it, * Get the route that created it
* Extract the `id` from the route * Extract the `id` from the route
* Acquire the hero with that `id` from the server via the `HeroService` * Acquire the hero with that `id` from the server via the `HeroService`
Add the following imports: Add the following imports:
<code-example <code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="added-imports" header="src/app/hero-detail/hero-detail.component.ts">
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts"
region="added-imports"
header="src/app/hero-detail/hero-detail.component.ts">
</code-example> </code-example>
{@a hero-detail-ctor} {@a hero-detail-ctor}
@ -409,27 +377,25 @@ Add the following imports:
Inject the `ActivatedRoute`, `HeroService`, and `Location` services Inject the `ActivatedRoute`, `HeroService`, and `Location` services
into the constructor, saving their values in private fields: into the constructor, saving their values in private fields:
<code-example <code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" header="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ctor">
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ctor">
</code-example> </code-example>
The [`ActivatedRoute`](api/router/ActivatedRoute) holds information about the route to this instance of the `HeroDetailComponent`. The [`ActivatedRoute`](api/router/ActivatedRoute) holds information about the route to this instance of the `HeroDetailComponent`.
This component is interested in the route's bag of parameters extracted from the URL. This component is interested in the route's parameters extracted from the URL.
The _"id"_ parameter is the `id` of the hero to display. The "id" parameter is the `id` of the hero to display.
The [`HeroService`](tutorial/toh-pt4) gets hero data from the remote server The [`HeroService`](tutorial/toh-pt4) gets hero data from the remote server
and this component will use it to get the _hero-to-display_. and this component will use it to get the hero-to-display.
The [`location`](api/common/Location) is an Angular service for interacting with the browser. The [`location`](api/common/Location) is an Angular service for interacting with the browser.
You'll use it [later](#goback) to navigate back to the view that navigated here. You'll use it [later](#goback) to navigate back to the view that navigated here.
### Extract the _id_ route parameter ### Extract the `id` route parameter
In the `ngOnInit()` [lifecycle hook](guide/lifecycle-hooks#oninit) In the `ngOnInit()` [lifecycle hook](guide/lifecycle-hooks#oninit)
call `getHero()` and define it as follows. call `getHero()` and define it as follows.
<code-example <code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" header="src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
</code-example> </code-example>
The `route.snapshot` is a static image of the route information shortly after the component was created. The `route.snapshot` is a static image of the route information shortly after the component was created.
@ -447,18 +413,14 @@ Add it now.
### Add `HeroService.getHero()` ### Add `HeroService.getHero()`
Open `HeroService` and add this `getHero()` method Open `HeroService` and add the following `getHero()` method with the `id` after the `getHeroes()` method:
<code-example <code-example path="toh-pt5/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts (getHero)">
path="toh-pt5/src/app/hero.service.ts"
region="getHero"
header="src/app/hero.service.ts (getHero)">
</code-example> </code-example>
<div class="alert is-important"> <div class="alert is-important">
Note the backticks ( &#96; ) that Note the backticks ( &#96; ) that define a JavaScript
define a JavaScript
[_template literal_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for embedding the `id`. [_template literal_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for embedding the `id`.
</div> </div>
@ -490,10 +452,7 @@ It would be nice to have a button on the `HeroDetail` view that can do that.
Add a *go back* button to the bottom of the component template and bind it Add a *go back* button to the bottom of the component template and bind it
to the component's `goBack()` method. to the component's `goBack()` method.
<code-example <code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.html" region="back-button" header="src/app/hero-detail/hero-detail.component.html (back button)">
path="toh-pt5/src/app/hero-detail/hero-detail.component.html"
region="back-button"
header="src/app/hero-detail/hero-detail.component.html (back button)">
</code-example> </code-example>
Add a `goBack()` _method_ to the component class that navigates backward one step Add a `goBack()` _method_ to the component class that navigates backward one step
@ -509,15 +468,13 @@ Refresh the browser and start clicking.
Users can navigate around the app, from the dashboard to hero details and back, Users can navigate around the app, from the dashboard to hero details and back,
from heroes list to the mini detail to the hero details and back to the heroes again. from heroes list to the mini detail to the hero details and back to the heroes again.
You've met all of the navigational requirements that propelled this page.
## Final code review ## Final code review
Here are the code files discussed on this page and your app should look like this <live-example></live-example>. Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
{@a approutingmodule} {@a approutingmodule}
{@a appmodule} {@a appmodule}
#### _AppRoutingModule_, _AppModule_, and _HeroService_ #### `AppRoutingModule`, `AppModule`, and `HeroService`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -535,7 +492,7 @@ Here are the code files discussed on this page and your app should look like thi
</code-tabs> </code-tabs>
{@a appcomponent} {@a appcomponent}
#### _AppComponent_ #### `AppComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -550,7 +507,7 @@ Here are the code files discussed on this page and your app should look like thi
</code-tabs> </code-tabs>
{@a dashboardcomponent} {@a dashboardcomponent}
#### _DashboardComponent_ #### `DashboardComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -567,7 +524,7 @@ Here are the code files discussed on this page and your app should look like thi
</code-tabs> </code-tabs>
{@a heroescomponent} {@a heroescomponent}
#### _HeroesComponent_ #### `HeroesComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -586,7 +543,7 @@ Here are the code files discussed on this page and your app should look like thi
</code-tabs> </code-tabs>
{@a herodetailcomponent} {@a herodetailcomponent}
#### _HeroDetailComponent_ #### `HeroDetailComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane

View File

@ -13,146 +13,119 @@ When you're done with this page, the app should look like this <live-example></l
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP. `HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
To make `HttpClient` available everywhere in the app: Make `HttpClient` available everywhere in the app in two steps. First, add it to the root `AppModule` by importing it:
* open the root `AppModule` <code-example path="toh-pt6/src/app/app.module.ts" region="import-http-client" header="src/app/app.module.ts (HttpClientModule import)">
* import the `HttpClientModule` symbol from `@angular/common/http` </code-example>
<code-example Next, still in the `AppModule`, add `HttpClient` to the `imports` array:
path="toh-pt6/src/app/app.module.ts"
region="import-http-client" <code-example path="toh-pt6/src/app/app.module.ts" region="import-httpclientmodule" header="src/app/app.module.ts (imports array excerpt)">
header="src/app/app.module.ts (Http Client import)">
</code-example> </code-example>
* add it to the `@NgModule.imports` array
## Simulate a data server ## Simulate a data server
This tutorial sample _mimics_ communication with a remote data server by using the This tutorial sample mimics communication with a remote data server by using the
[_In-memory Web API_](https://github.com/angular/in-memory-web-api "In-memory Web API") module. [In-memory Web API](https://github.com/angular/in-memory-web-api "In-memory Web API") module.
After installing the module, the app will make requests to and receive responses from the `HttpClient` After installing the module, the app will make requests to and receive responses from the `HttpClient`
without knowing that the *In-memory Web API* is intercepting those requests, without knowing that the *In-memory Web API* is intercepting those requests,
applying them to an in-memory data store, and returning simulated responses. applying them to an in-memory data store, and returning simulated responses.
This facility is a great convenience for the tutorial. By using the In-memory Web API, you won't have to set up a server to learn about `HttpClient`.
You won't have to set up a server to learn about `HttpClient`.
It may also be convenient in the early stages of your own app development when
the server's web api is ill-defined or not yet implemented.
<div class="alert is-important"> <div class="alert is-important">
**Important:** the *In-memory Web API* module has nothing to do with HTTP in Angular. **Important:** the In-memory Web API module has nothing to do with HTTP in Angular.
If you're just _reading_ this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step. If you're just reading this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step.
If you're _coding along_ with this tutorial, stay here and add the *In-memory Web API* now. If you're coding along with this tutorial, stay here and add the In-memory Web API now.
</div> </div>
Install the *In-memory Web API* package from _npm_ Install the In-memory Web API package from npm with the following command:
<code-example language="sh" class="code-shell"> <code-example language="sh" class="code-shell">
npm install angular-in-memory-web-api --save npm install angular-in-memory-web-api --save
</code-example> </code-example>
In the `AppModule`, import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class,
which you will create in a moment.
The class `src/app/in-memory-data.service.ts` is generated by the following command: <code-example path="toh-pt6/src/app/app.module.ts" region="import-in-mem-stuff" header="src/app/app.module.ts (In-memory Web API imports)">
<code-example language="sh" class="code-shell">
ng generate service InMemoryData
</code-example> </code-example>
This class has the following content: After the `HttpClientModule`, add the `HttpClientInMemoryWebApiModule`
to the `AppModule` `imports` array and configure it with the `InMemoryDataService`.
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts" linenums="false"></code-example> <code-example path="toh-pt6/src/app/app.module.ts" header="src/app/app.module.ts (imports array excerpt)" region="in-mem-web-api-imports">
This file replaces `mock-heroes.ts`, which is now safe to delete.
When your server is ready, detach the *In-memory Web API*, and the app's requests will go through to the server.
Now back to the `HttpClient` story.
Import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class.
<code-example
path="toh-pt6/src/app/app.module.ts"
region="import-in-mem-stuff"
header="src/app/app.module.ts (In-memory Web API imports)">
</code-example>
Add the `HttpClientInMemoryWebApiModule` to the `@NgModule.imports` array&mdash;
_after importing the `HttpClientModule`_,
&mdash;while configuring it with the `InMemoryDataService`.
<code-example
path="toh-pt6/src/app/app.module.ts"
region="in-mem-web-api-imports">
</code-example> </code-example>
The `forRoot()` configuration method takes an `InMemoryDataService` class The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database. that primes the in-memory database.
Generate the class `src/app/in-memory-data.service.ts` with the following command:
<code-example language="sh" class="code-shell">
ng generate service InMemoryData
</code-example>
Replace the default contents of `in-memory-data.service.ts` with the following:
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts" linenums="false"></code-example>
The `in-memory-data.service.ts` file replaces `mock-heroes.ts`, which is now safe to delete.
When the server is ready, you'll detach the In-memory Web API, and the app's requests will go through to the server.
{@a import-heroes} {@a import-heroes}
## Heroes and HTTP ## Heroes and HTTP
Import some HTTP symbols that you'll need: In the `HeroService`, import `HttpClient` and `HttpHeaders`:
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" region="import-httpclient" header="src/app/hero.service.ts (import HTTP symbols)">
path="toh-pt6/src/app/hero.service.ts"
region="import-httpclient"
header="src/app/hero.service.ts (import HTTP symbols)">
</code-example> </code-example>
Inject `HttpClient` into the constructor in a private property called `http`. Still in the `HeroService`, inject `HttpClient` into the constructor in a private property called `http`.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor" >
path="toh-pt6/src/app/hero.service.ts"
region="ctor" >
</code-example> </code-example>
Keep injecting the `MessageService`. You'll call it so frequently that Notice that you keep injecting the `MessageService` but since you'll call it so frequently, wrap it in a private `log()` method:
you'll wrap it in a private `log()` method.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="log" >
path="toh-pt6/src/app/hero.service.ts"
region="log" >
</code-example> </code-example>
Define the `heroesUrl` of the form `:base/:collectionName` with the address of the heroes resource on the server. Define the `heroesUrl` of the form `:base/:collectionName` with the address of the heroes resource on the server.
Here `base` is the resource to which requests are made, Here `base` is the resource to which requests are made,
and `collectionName` is the heroes data object in the `in-memory-data-service.ts`. and `collectionName` is the heroes data object in the `in-memory-data-service.ts`.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="heroesUrl" >
path="toh-pt6/src/app/hero.service.ts"
region="heroesUrl" >
</code-example> </code-example>
### Get heroes with _HttpClient_ ### Get heroes with `HttpClient`
The current `HeroService.getHeroes()` The current `HeroService.getHeroes()`
uses the RxJS `of()` function to return an array of mock heroes uses the RxJS `of()` function to return an array of mock heroes
as an `Observable<Hero[]>`. as an `Observable<Hero[]>`.
<code-example <code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1" header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
path="toh-pt4/src/app/hero.service.ts"
region="getHeroes-1"
header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
</code-example> </code-example>
Convert that method to use `HttpClient` Convert that method to use `HttpClient` as follows:
<code-example
path="toh-pt6/src/app/hero.service.ts" <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1">
region="getHeroes-1">
</code-example> </code-example>
Refresh the browser. The hero data should successfully load from the Refresh the browser. The hero data should successfully load from the
mock server. mock server.
You've swapped `of` for `http.get` and the app keeps working without any other changes You've swapped `of()` for `http.get()` and the app keeps working without any other changes
because both functions return an `Observable<Hero[]>`. because both functions return an `Observable<Hero[]>`.
### Http methods return one value ### `HttpClient` methods return one value
All `HttpClient` methods return an RxJS `Observable` of something. All `HttpClient` methods return an RxJS `Observable` of something.
@ -162,23 +135,23 @@ You make a request, it returns a single response.
In general, an observable _can_ return multiple values over time. In general, an observable _can_ return multiple values over time.
An observable from `HttpClient` always emits a single value and then completes, never to emit again. An observable from `HttpClient` always emits a single value and then completes, never to emit again.
This particular `HttpClient.get` call returns an `Observable<Hero[]>`, literally "_an observable of hero arrays_". In practice, it will only return a single hero array. This particular `HttpClient.get()` call returns an `Observable<Hero[]>`; that is, "_an observable of hero arrays_". In practice, it will only return a single hero array.
### _HttpClient.get_ returns response data ### `HttpClient.get()` returns response data
`HttpClient.get` returns the _body_ of the response as an untyped JSON object by default. `HttpClient.get()` returns the body of the response as an untyped JSON object by default.
Applying the optional type specifier, `<Hero[]>` , gives you a typed result object. Applying the optional type specifier, `<Hero[]>` , gives you a typed result object.
The shape of the JSON data is determined by the server's data API. The server's data API determines the shape of the JSON data.
The _Tour of Heroes_ data API returns the hero data as an array. The _Tour of Heroes_ data API returns the hero data as an array.
<div class="alert is-helpful"> <div class="alert is-helpful">
Other APIs may bury the data that you want within an object. Other APIs may bury the data that you want within an object.
You might have to dig that data out by processing the `Observable` result You might have to dig that data out by processing the `Observable` result
with the RxJS `map` operator. with the RxJS `map()` operator.
Although not discussed here, there's an example of `map` in the `getHeroNo404()` Although not discussed here, there's an example of `map()` in the `getHeroNo404()`
method included in the sample source code. method included in the sample source code.
</div> </div>
@ -192,59 +165,51 @@ To catch errors, you **"pipe" the observable** result from `http.get()` through
Import the `catchError` symbol from `rxjs/operators`, along with some other operators you'll need later. Import the `catchError` symbol from `rxjs/operators`, along with some other operators you'll need later.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="import-rxjs-operators">
path="toh-pt6/src/app/hero.service.ts"
region="import-rxjs-operators">
</code-example> </code-example>
Now extend the observable result with the `.pipe()` method and Now extend the observable result with the `pipe()` method and
give it a `catchError()` operator. give it a `catchError()` operator.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" region="getHeroes-2" header="src/app/hero.service.ts">
path="toh-pt6/src/app/hero.service.ts"
region="getHeroes-2" >
</code-example> </code-example>
The `catchError()` operator intercepts an **`Observable` that failed**. The `catchError()` operator intercepts an **`Observable` that failed**.
It passes the error an _error handler_ that can do what it wants with the error. It passes the error an error handler that can do what it wants with the error.
The following `handleError()` method reports the error and then returns an The following `handleError()` method reports the error and then returns an
innocuous result so that the application keeps working. innocuous result so that the application keeps working.
#### _handleError_ #### `handleError`
The following `handleError()` will be shared by many `HeroService` methods The following `handleError()` will be shared by many `HeroService` methods
so it's generalized to meet their different needs. so it's generalized to meet their different needs.
Instead of handling the error directly, it returns an _error handler_ function to `catchError` that it Instead of handling the error directly, it returns an error handler function to `catchError` that it
has configured with both the name of the operation that failed and a safe return value. has configured with both the name of the operation that failed and a safe return value.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="handleError">
path="toh-pt6/src/app/hero.service.ts"
region="handleError">
</code-example> </code-example>
After reporting the error to console, the handler constructs After reporting the error to the console, the handler constructs
a user friendly message and returns a safe value to the app so it can keep working. a user friendly message and returns a safe value to the app so the app can keep working.
Because each service method returns a different kind of `Observable` result, Because each service method returns a different kind of `Observable` result,
`handleError()` takes a type parameter so it can return the safe value as the type that the app expects. `handleError()` takes a type parameter so it can return the safe value as the type that the app expects.
### Tap into the _Observable_ ### Tap into the Observable
The `HeroService` methods will **tap** into the flow of observable values The `HeroService` methods will **tap** into the flow of observable values
and send a message (via `log()`) to the message area at the bottom of the page. and send a message, via the `log()` method, to the message area at the bottom of the page.
They'll do that with the RxJS `tap` operator, They'll do that with the RxJS `tap()` operator,
which _looks_ at the observable values, does _something_ with those values, which looks at the observable values, does something with those values,
and passes them along. and passes them along.
The `tap` call back doesn't touch the values themselves. The `tap()` call back doesn't touch the values themselves.
Here is the final version of `getHeroes` with the `tap` that logs the operation. Here is the final version of `getHeroes()` with the `tap()` that logs the operation.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes" >
path="toh-pt6/src/app/hero.service.ts"
region="getHeroes" >
</code-example> </code-example>
### Get hero by id ### Get hero by id
@ -254,20 +219,20 @@ Most web APIs support a _get by id_ request in the form `:baseURL/:id`.
Here, the _base URL_ is the `heroesURL` defined in the [Heroes and HTTP](tutorial/toh-pt6#heroes-and-http) section (`api/heroes`) and _id_ is Here, the _base URL_ is the `heroesURL` defined in the [Heroes and HTTP](tutorial/toh-pt6#heroes-and-http) section (`api/heroes`) and _id_ is
the number of the hero that you want to retrieve. For example, `api/heroes/11`. the number of the hero that you want to retrieve. For example, `api/heroes/11`.
Add a `HeroService.getHero()` method to make that request: Update the `HeroService` `getHero()` method with the following to make that request:
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts"></code-example> <code-example path="toh-pt6/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts"></code-example>
There are three significant differences from `getHeroes()`. There are three significant differences from `getHeroes()`:
* it constructs a request URL with the desired hero's id. * `getHero()` constructs a request URL with the desired hero's id.
* the server should respond with a single hero rather than an array of heroes. * The server should respond with a single hero rather than an array of heroes.
* therefore, `getHero` returns an `Observable<Hero>` ("_an observable of Hero objects_") * `getHero()` returns an `Observable<Hero>` ("_an observable of Hero objects_")
rather than an observable of hero _arrays_ . rather than an observable of hero _arrays_ .
## Update heroes ## Update heroes
Edit a hero's name in the _hero detail_ view. Edit a hero's name in the hero detail view.
As you type, the hero name updates the heading at the top of the page. As you type, the hero name updates the heading at the top of the page.
But when you click the "go back button", the changes are lost. But when you click the "go back button", the changes are lost.
@ -279,24 +244,21 @@ binding that invokes a new component method named `save()`.
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.html" region="save" header="src/app/hero-detail/hero-detail.component.html (save)"></code-example> <code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.html" region="save" header="src/app/hero-detail/hero-detail.component.html (save)"></code-example>
Add the following `save()` method, which persists hero name changes using the hero service In the `HeroDetail` component class, add the following `save()` method, which persists hero name changes using the hero service
`updateHero()` method and then navigates back to the previous view. `updateHero()` method and then navigates back to the previous view.
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.ts" region="save" header="src/app/hero-detail/hero-detail.component.ts (save)"></code-example> <code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.ts" region="save" header="src/app/hero-detail/hero-detail.component.ts (save)"></code-example>
#### Add _HeroService.updateHero()_ #### Add `HeroService.updateHero()`
The overall structure of the `updateHero()` method is similar to that of The overall structure of the `updateHero()` method is similar to that of
`getHeroes()`, but it uses `http.put()` to persist the changed hero `getHeroes()`, but it uses `http.put()` to persist the changed hero
on the server. on the server. Add the following to the `HeroService`.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" region="updateHero" header="src/app/hero.service.ts (update)">
path="toh-pt6/src/app/hero.service.ts"
region="updateHero"
header="src/app/hero.service.ts (update)">
</code-example> </code-example>
The `HttpClient.put()` method takes three parameters The `HttpClient.put()` method takes three parameters:
* the URL * the URL
* the data to update (the modified hero in this case) * the data to update (the modified hero in this case)
* options * options
@ -304,20 +266,19 @@ The `HttpClient.put()` method takes three parameters
The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id`. The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id`.
The heroes web API expects a special header in HTTP save requests. The heroes web API expects a special header in HTTP save requests.
That header is in the `httpOptions` constant defined in the `HeroService`. That header is in the `httpOptions` constant defined in the `HeroService`. Add the following to the `HeroService` class.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" region="http-options" header="src/app/hero.service.ts">
path="toh-pt6/src/app/hero.service.ts"
region="http-options"
header="src/app/hero.service.ts">
</code-example> </code-example>
Refresh the browser, change a hero name and save your change. Navigating to the previous view is implemented in the `save()` method defined in `HeroDetailComponent`. Refresh the browser, change a hero name and save your change. The `save()`
method in `HeroDetailComponent`navigates to the previous view.
The hero now appears in the list with the changed name. The hero now appears in the list with the changed name.
## Add a new hero ## Add a new hero
To add a hero, this app only needs the hero's name. You can use an `input` To add a hero, this app only needs the hero's name. You can use an `<input>`
element paired with an add button. element paired with an add button.
Insert the following into the `HeroesComponent` template, just after Insert the following into the `HeroesComponent` template, just after
@ -325,29 +286,26 @@ the heading:
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="add" header="src/app/heroes/heroes.component.html (add)"></code-example> <code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="add" header="src/app/heroes/heroes.component.html (add)"></code-example>
In response to a click event, call the component's click handler and then In response to a click event, call the component's click handler, `add()`, and then
clear the input field so that it's ready for another name. clear the input field so that it's ready for another name. Add the following to the
`HeroesComponent` class:
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="add" header="src/app/heroes/heroes.component.ts (add)"></code-example> <code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="add" header="src/app/heroes/heroes.component.ts (add)"></code-example>
When the given name is non-blank, the handler creates a `Hero`-like object When the given name is non-blank, the handler creates a `Hero`-like object
from the name (it's only missing the `id`) and passes it to the services `addHero()` method. from the name (it's only missing the `id`) and passes it to the services `addHero()` method.
When `addHero` saves successfully, the `subscribe` callback When `addHero()` saves successfully, the `subscribe()` callback
receives the new hero and pushes it into to the `heroes` list for display. receives the new hero and pushes it into to the `heroes` list for display.
You'll write `HeroService.addHero` in the next section.
#### Add _HeroService.addHero()_
Add the following `addHero()` method to the `HeroService` class. Add the following `addHero()` method to the `HeroService` class.
<code-example path="toh-pt6/src/app/hero.service.ts" region="addHero" header="src/app/hero.service.ts (addHero)"></code-example> <code-example path="toh-pt6/src/app/hero.service.ts" region="addHero" header="src/app/hero.service.ts (addHero)"></code-example>
`HeroService.addHero()` differs from `updateHero` in two ways. `addHero()` differs from `updateHero()` in two ways:
* it calls `HttpClient.post()` instead of `put()`. * It calls `HttpClient.post()` instead of `put()`.
* it expects the server to generate an id for the new hero, * It expects the server to generate an id for the new hero,
which it returns in the `Observable<Hero>` to the caller. which it returns in the `Observable<Hero>` to the caller.
Refresh the browser and add some heroes. Refresh the browser and add some heroes.
@ -359,7 +317,7 @@ Each hero in the heroes list should have a delete button.
Add the following button element to the `HeroesComponent` template, after the hero Add the following button element to the `HeroesComponent` template, after the hero
name in the repeated `<li>` element. name in the repeated `<li>` element.
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="delete"></code-example> <code-example path="toh-pt6/src/app/heroes/heroes.component.html" header="src/app/hero.service.ts" region="delete"></code-example>
The HTML for the list of heroes should look like this: The HTML for the list of heroes should look like this:
@ -369,7 +327,7 @@ To position the delete button at the far right of the hero entry,
add some CSS to the `heroes.component.css`. You'll find that CSS add some CSS to the `heroes.component.css`. You'll find that CSS
in the [final review code](#heroescomponent) below. in the [final review code](#heroescomponent) below.
Add the `delete()` handler to the component. Add the `delete()` handler to the component class.
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example> <code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example>
@ -379,12 +337,12 @@ The component's `delete()` method immediately removes the _hero-to-delete_ from
anticipating that the `HeroService` will succeed on the server. anticipating that the `HeroService` will succeed on the server.
There's really nothing for the component to do with the `Observable` returned by There's really nothing for the component to do with the `Observable` returned by
`heroService.delete()`. **It must subscribe anyway**. `heroService.delete()` **but it must subscribe anyway**.
<div class="alert is-important"> <div class="alert is-important">
If you neglect to `subscribe()`, the service will not send the delete request to the server! If you neglect to `subscribe()`, the service will not send the delete request to the server.
As a rule, an `Observable` _does nothing_ until something subscribes! As a rule, an `Observable` _does nothing_ until something subscribes.
Confirm this for yourself by temporarily removing the `subscribe()`, Confirm this for yourself by temporarily removing the `subscribe()`,
clicking "Dashboard", then clicking "Heroes". clicking "Dashboard", then clicking "Heroes".
@ -392,18 +350,16 @@ There's really nothing for the component to do with the `Observable` returned by
</div> </div>
#### Add _HeroService.deleteHero()_ Next, add a `deleteHero()` method to `HeroService` like this.
Add a `deleteHero()` method to `HeroService` like this.
<code-example path="toh-pt6/src/app/hero.service.ts" region="deleteHero" header="src/app/hero.service.ts (delete)"></code-example> <code-example path="toh-pt6/src/app/hero.service.ts" region="deleteHero" header="src/app/hero.service.ts (delete)"></code-example>
Note that Note the following key points:
* it calls `HttpClient.delete`. * `deleteHero()` calls `HttpClient.delete()`.
* the URL is the heroes resource URL plus the `id` of the hero to delete * The URL is the heroes resource URL plus the `id` of the hero to delete.
* you don't send data as you did with `put` and `post`. * You don't send data as you did with `put()` and `post()`.
* you still send the `httpOptions`. * You still send the `httpOptions`.
Refresh the browser and try the new delete functionality. Refresh the browser and try the new delete functionality.
@ -413,43 +369,36 @@ In this last exercise, you learn to chain `Observable` operators together
so you can minimize the number of similar HTTP requests so you can minimize the number of similar HTTP requests
and consume network bandwidth economically. and consume network bandwidth economically.
You will add a *heroes search* feature to the *Dashboard*. You will add a heroes search feature to the Dashboard.
As the user types a name into a search box, As the user types a name into a search box,
you'll make repeated HTTP requests for heroes filtered by that name. you'll make repeated HTTP requests for heroes filtered by that name.
Your goal is to issue only as many requests as necessary. Your goal is to issue only as many requests as necessary.
#### _HeroService.searchHeroes_ #### `HeroService.searchHeroes()`
Start by adding a `searchHeroes` method to the `HeroService`. Start by adding a `searchHeroes()` method to the `HeroService`.
<code-example <code-example path="toh-pt6/src/app/hero.service.ts" region="searchHeroes" header="src/app/hero.service.ts">
path="toh-pt6/src/app/hero.service.ts"
region="searchHeroes"
header="src/app/hero.service.ts">
</code-example> </code-example>
The method returns immediately with an empty array if there is no search term. The method returns immediately with an empty array if there is no search term.
The rest of it closely resembles `getHeroes()`. The rest of it closely resembles `getHeroes()`, the only significant difference being
The only significant difference is the URL, the URL, which includes a query string with the search term.
which includes a query string with the search term.
### Add search to the Dashboard ### Add search to the Dashboard
Open the `DashboardComponent` _template_ and Open the `DashboardComponent` template and
Add the hero search element, `<app-hero-search>`, to the bottom of the `DashboardComponent` template. add the hero search element, `<app-hero-search>`, to the bottom of the markup.
<code-example <code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
</code-example> </code-example>
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template. This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
Unfortunately, adding this element breaks the app. For this to work, the next step is to add a component with a selector that matches `<app-hero-search>`.
Angular can't find a component with a selector that matches `<app-hero-search>`.
The `HeroSearchComponent` doesn't exist yet. Fix that.
### Create _HeroSearchComponent_ ### Create `HeroSearchComponent`
Create a `HeroSearchComponent` with the CLI. Create a `HeroSearchComponent` with the CLI.
@ -457,68 +406,60 @@ Create a `HeroSearchComponent` with the CLI.
ng generate component hero-search ng generate component hero-search
</code-example> </code-example>
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations.
Replace the generated `HeroSearchComponent` _template_ with a text box and a list of matching search results like this. Replace the generated `HeroSearchComponent` template with an `<input>` and a list of matching search results, as follows.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html"></code-example> <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html"></code-example>
Add private CSS styles to `hero-search.component.css` Add private CSS styles to `hero-search.component.css`
as listed in the [final code review](#herosearchcomponent) below. as listed in the [final code review](#herosearchcomponent) below.
As the user types in the search box, an *input* event binding calls the component's `search()` As the user types in the search box, an input event binding calls the
method with the new search box value. component's `search()` method with the new search box value.
{@a asyncpipe} {@a asyncpipe}
### _AsyncPipe_ ### `AsyncPipe`
As expected, the `*ngFor` repeats hero objects. The `*ngFor` repeats hero objects. Notice that the `*ngFor` iterates over a list called `heroes$`, not `heroes`. The `$` is a convention that indicates `heroes$` is an `Observable`, not an array.
Look closely and you'll see that the `*ngFor` iterates over a list called `heroes$`, not `heroes`.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="async"></code-example> <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html" region="async"></code-example>
The `$` is a convention that indicates `heroes$` is an `Observable`, not an array. Since `*ngFor` can't do anything with an `Observable`, use the
pipe character (`|`) followed by `async`. This identifies Angular's `AsyncPipe` and subscribes to an `Observable` automatically so you won't have to
The `*ngFor` can't do anything with an `Observable`.
But there's also a pipe character (`|`) followed by `async`,
which identifies Angular's `AsyncPipe`.
The `AsyncPipe` subscribes to an `Observable` automatically so you won't have to
do so in the component class. do so in the component class.
### Fix the _HeroSearchComponent_ class ### Edit the `HeroSearchComponent` class
Replace the generated `HeroSearchComponent` class and metadata as follows. Replace the generated `HeroSearchComponent` class and metadata as follows.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts"></code-example> <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts"></code-example>
Notice the declaration of `heroes$` as an `Observable` Notice the declaration of `heroes$` as an `Observable`:
<code-example
path="toh-pt6/src/app/hero-search/hero-search.component.ts" <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="heroes-stream">
region="heroes-stream">
</code-example> </code-example>
You'll set it in [`ngOnInit()`](#search-pipe). You'll set it in [`ngOnInit()`](#search-pipe).
Before you do, focus on the definition of `searchTerms`. Before you do, focus on the definition of `searchTerms`.
### The _searchTerms_ RxJS subject ### The `searchTerms` RxJS subject
The `searchTerms` property is declared as an RxJS `Subject`. The `searchTerms` property is an RxJS `Subject`.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" region="searchTerms"></code-example> <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="searchTerms"></code-example>
A `Subject` is both a source of _observable_ values and an `Observable` itself. A `Subject` is both a source of observable values and an `Observable` itself.
You can subscribe to a `Subject` as you would any `Observable`. You can subscribe to a `Subject` as you would any `Observable`.
You can also push values into that `Observable` by calling its `next(value)` method You can also push values into that `Observable` by calling its `next(value)` method
as the `search()` method does. as the `search()` method does.
The `search()` method is called via an _event binding_ to the The event binding to the textbox's `input` event calls the `search()` method.
textbox's `input` event.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="input"></code-example> <code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html" region="input"></code-example>
Every time the user types in the textbox, the binding calls `search()` with the textbox value, a "search term". Every time the user types in the textbox, the binding calls `search()` with the textbox value, a "search term".
The `searchTerms` becomes an `Observable` emitting a steady stream of search terms. The `searchTerms` becomes an `Observable` emitting a steady stream of search terms.
@ -528,28 +469,24 @@ The `searchTerms` becomes an `Observable` emitting a steady stream of search ter
### Chaining RxJS operators ### Chaining RxJS operators
Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests, Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests,
taxing server resources and burning through the cellular network data plan. taxing server resources and burning through data plans.
Instead, the `ngOnInit()` method pipes the `searchTerms` observable through a sequence of RxJS operators that reduce the number of calls to the `searchHeroes()`, Instead, the `ngOnInit()` method pipes the `searchTerms` observable through a sequence of RxJS operators that reduce the number of calls to the `searchHeroes()`,
ultimately returning an observable of timely hero search results (each a `Hero[]`). ultimately returning an observable of timely hero search results (each a `Hero[]`).
Here's the code. Here's a closer look at the code.
<code-example <code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="search">
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
region="search">
</code-example> </code-example>
Each operator works as follows:
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds * `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
before passing along the latest string. You'll never make requests more frequently than 300ms. 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. * `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()`.
* `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. It cancels and discards previous search observables, returning only the latest search service observable.
@ -563,7 +500,7 @@ It cancels and discards previous search observables, returning only the latest s
`switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call. `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. Results from prior calls are canceled and discarded.
Note that _canceling_ a previous `searchHeroes()` _Observable_ Note that canceling a previous `searchHeroes()` Observable
doesn't actually abort a pending HTTP request. doesn't actually abort a pending HTTP request.
Unwanted results are simply discarded before they reach your application code. Unwanted results are simply discarded before they reach your application code.
@ -590,7 +527,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
{@a heroservice} {@a heroservice}
{@a inmemorydataservice} {@a inmemorydataservice}
{@a appmodule} {@a appmodule}
#### _HeroService_, _InMemoryDataService_, _AppModule_ #### `HeroService`, `InMemoryDataService`, `AppModule`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -608,7 +545,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
</code-tabs> </code-tabs>
{@a heroescomponent} {@a heroescomponent}
#### _HeroesComponent_ #### `HeroesComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -626,7 +563,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
</code-tabs> </code-tabs>
{@a herodetailcomponent} {@a herodetailcomponent}
#### _HeroDetailComponent_ #### `HeroDetailComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -640,7 +577,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
</code-tabs> </code-tabs>
{@a dashboardcomponent} {@a dashboardcomponent}
#### _DashboardComponent_ #### `DashboardComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane
@ -650,7 +587,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
</code-tabs> </code-tabs>
{@a herosearchcomponent} {@a herosearchcomponent}
#### _HeroSearchComponent_ #### `HeroSearchComponent`
<code-tabs> <code-tabs>
<code-pane <code-pane