parent
cb848b9410
commit
8f084d7214
|
@ -1,8 +1,4 @@
|
|||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
|
@ -19,18 +15,18 @@
|
|||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
|
|
|
@ -34,4 +34,7 @@ export class HeroesComponent implements OnInit {
|
|||
this.selectedHero = hero;
|
||||
}
|
||||
// #enddocregion on-select
|
||||
// #docregion component
|
||||
}
|
||||
// #enddocregion component
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
|
@ -19,18 +15,18 @@
|
|||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
|
@ -19,18 +15,18 @@
|
|||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
|
@ -43,8 +39,6 @@
|
|||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
|
|
@ -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 { }
|
|
@ -7,9 +7,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
// #docregion import-dashboard
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
// #enddocregion import-dashboard
|
||||
// #docregion heroes-route
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
// #enddocregion heroes-route
|
||||
// #docregion import-herodetail
|
||||
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
|
||||
// #enddocregion import-herodetail
|
||||
|
@ -39,7 +37,9 @@ const routes: Routes = [
|
|||
imports: [ RouterModule.forRoot(routes) ],
|
||||
// #enddocregion ngmodule-imports
|
||||
// #docregion v1
|
||||
// #docregion export-routermodule
|
||||
exports: [ RouterModule ]
|
||||
// #enddocregion export-routermodule
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
// #enddocregion , v1
|
||||
|
|
|
@ -23,13 +23,17 @@ import { HeroSearchComponent } from './hero-search/hero-search.component';
|
|||
// #docregion v1
|
||||
import { MessagesComponent } from './messages/messages.component';
|
||||
|
||||
// #docregion import-httpclientmodule
|
||||
@NgModule({
|
||||
imports: [
|
||||
// #enddocregion import-httpclientmodule
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
AppRoutingModule,
|
||||
// #docregion in-mem-web-api-imports
|
||||
// #docregion import-httpclientmodule
|
||||
HttpClientModule,
|
||||
// #enddocregion import-httpclientmodule
|
||||
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
|
@ -38,7 +42,9 @@ import { MessagesComponent } from './messages/messages.component';
|
|||
InMemoryDataService, { dataEncapsulation: false }
|
||||
)
|
||||
// #enddocregion in-mem-web-api-imports
|
||||
// #docregion import-httpclientmodule
|
||||
],
|
||||
// #enddocregion import-httpclientmodule
|
||||
declarations: [
|
||||
AppComponent,
|
||||
DashboardComponent,
|
||||
|
@ -50,6 +56,9 @@ import { MessagesComponent } from './messages/messages.component';
|
|||
// #docregion v1
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
// #docregion import-httpclientmodule
|
||||
})
|
||||
// #enddocregion import-httpclientmodule
|
||||
|
||||
export class AppModule { }
|
||||
// #enddocregion , v1
|
||||
|
|
|
@ -13,11 +13,6 @@ import { catchError, map, tap } from 'rxjs/operators';
|
|||
import { Hero } from './hero';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
// #docregion http-options
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
// #enddocregion http-options
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HeroService {
|
||||
|
@ -26,6 +21,12 @@ export class HeroService {
|
|||
private heroesUrl = 'api/heroes'; // URL to web api
|
||||
// #enddocregion heroesUrl
|
||||
|
||||
// #docregion http-options
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
// #enddocregion http-options
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
|
@ -96,7 +97,7 @@ export class HeroService {
|
|||
// #docregion addHero
|
||||
/** POST: add a new hero to the server */
|
||||
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}`)),
|
||||
catchError(this.handleError<Hero>('addHero'))
|
||||
);
|
||||
|
@ -109,7 +110,7 @@ export class HeroService {
|
|||
const id = typeof hero === 'number' ? hero : hero.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}`)),
|
||||
catchError(this.handleError<Hero>('deleteHero'))
|
||||
);
|
||||
|
@ -119,7 +120,7 @@ export class HeroService {
|
|||
// #docregion updateHero
|
||||
/** PUT: update the hero on the server */
|
||||
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}`)),
|
||||
catchError(this.handleError<any>('updateHero'))
|
||||
);
|
||||
|
|
|
@ -13,13 +13,11 @@ Using the Angular CLI, generate a new component named `heroes`.
|
|||
</code-example>
|
||||
|
||||
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:
|
||||
|
||||
<code-example
|
||||
path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1"
|
||||
header="app/heroes/heroes.component.ts (initial version)" linenums="false">
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1" header="app/heroes/heroes.component.ts (initial version)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
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),
|
||||
`'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).
|
||||
Angular calls `ngOnInit` shortly after creating a component.
|
||||
The `ngOnInit()` is a [lifecycle hook](guide/lifecycle-hooks#oninit).
|
||||
Angular calls `ngOnInit()` shortly after creating a component.
|
||||
It's a good place to put initialization logic.
|
||||
|
||||
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."
|
||||
|
||||
|
@ -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>
|
||||
|
||||
## Show the _HeroesComponent_ view
|
||||
## Show the `HeroesComponent` view
|
||||
|
||||
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
|
||||
and show both `id` and `name` in a details layout like this:
|
||||
|
||||
<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">
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
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_
|
||||
|
||||
Modify the `hero.name` binding like this.
|
||||
<code-example
|
||||
path="toh-pt1/src/app/heroes/heroes.component.html"
|
||||
region="pipe">
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="pipe">
|
||||
</code-example>
|
||||
|
||||
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
|
||||
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_.
|
||||
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
### Declare _HeroesComponent_
|
||||
### Declare `HeroesComponent`
|
||||
|
||||
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.
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
|
||||
Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`.
|
||||
|
|
|
@ -21,19 +21,17 @@ header="src/app/mock-heroes.ts">
|
|||
|
||||
## 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`.
|
||||
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
### List heroes with _*ngFor_
|
||||
### List heroes with `*ngFor`
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
|
@ -55,10 +53,10 @@ Now change the `<li>` to this:
|
|||
The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive.
|
||||
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
|
||||
* `heroes` is the list from the `HeroesComponent` class.
|
||||
* `<li>` is the host element.
|
||||
* `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.
|
||||
|
||||
<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.
|
||||
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>`,
|
||||
the same `hero` defined previously in the `*ngFor` expression.
|
||||
|
||||
In the next section, define an `onSelect()` method in `HeroesComponent` to
|
||||
display the hero that was defined in the `*ngFor` expression.
|
||||
|
||||
|
||||
### 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>
|
||||
|
||||
### Update the details template
|
||||
### Add a details section
|
||||
|
||||
The template still refers to the component's old `hero` property which no longer exists.
|
||||
Rename `hero` to `selectedHero`.
|
||||
Currently, you have a list in the component template. To click on a hero on the list
|
||||
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>
|
||||
|
@ -192,7 +192,7 @@ The heroes appear in a list and details about the clicked hero appear at the bot
|
|||
|
||||
#### 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
|
||||
`ngIf` puts the hero detail into the DOM.
|
||||
|
|
|
@ -72,7 +72,7 @@ Amend the `@angular/core` import statement to include the `Input` symbol.
|
|||
|
||||
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>
|
||||
|
||||
That's the only change you should make to the `HeroDetailComponent` class.
|
||||
|
|
|
@ -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_.
|
||||
You'll create a `MessageService` and inject it in two places:
|
||||
|
||||
1. in `HeroService` which uses the service to send a message.
|
||||
2. in `MessagesComponent` which displays that message.
|
||||
1. in `HeroService` which uses the service to send a message
|
||||
2. in `MessagesComponent` which displays that message
|
||||
|
||||
|
||||
## Create the _HeroService_
|
||||
## Create the `HeroService`
|
||||
|
||||
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
|
||||
</code-example>
|
||||
|
||||
The command generates skeleton `HeroService` class in `src/app/hero.service.ts`
|
||||
The `HeroService` class should look like the following example.
|
||||
The command generates a skeleton `HeroService` class in `src/app/hero.service.ts` as follows:
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
|
||||
header="src/app/hero.service.ts (new service)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### _@Injectable()_ services
|
||||
|
||||
### `@Injectable()` services
|
||||
|
||||
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.
|
||||
|
@ -56,27 +56,25 @@ The implementation in _this_ tutorial will continue to deliver _mock 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>
|
||||
|
||||
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>
|
||||
|
||||
{@a provide}
|
||||
## Provide the `HeroService`
|
||||
|
||||
You must make the `HeroService` available to the dependency injection system
|
||||
before Angular can _inject_ it into the `HeroesComponent`,
|
||||
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.
|
||||
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.
|
||||
|
||||
Now, you need to make sure that the `HeroService` is registered as the provider of this service.
|
||||
You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required.
|
||||
To make sure that the `HeroService` can provide this service, register it
|
||||
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.
|
||||
|
||||
If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root':
|
||||
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.
|
||||
|
||||
```
|
||||
@Injectable({
|
||||
|
@ -115,7 +113,7 @@ Import the `HeroService` instead.
|
|||
|
||||
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>
|
||||
|
||||
{@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.
|
||||
|
||||
<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>
|
||||
|
||||
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
|
||||
sets the `heroService` parameter to the singleton instance of `HeroService`.
|
||||
|
||||
### Add _getHeroes()_
|
||||
### Add `getHeroes()`
|
||||
|
||||
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>
|
||||
|
||||
{@a oninit}
|
||||
|
||||
### Call it in `ngOnInit`
|
||||
### Call it in `ngOnInit()`
|
||||
|
||||
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.
|
||||
|
||||
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>
|
||||
|
||||
### See it run
|
||||
|
@ -167,7 +165,7 @@ which implies that the `HeroService` can fetch heroes synchronously.
|
|||
The `HeroesComponent` consumes the `getHeroes()` result
|
||||
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>
|
||||
|
||||
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.
|
||||
|
||||
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 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).
|
||||
|
||||
### Observable _HeroService_
|
||||
### Observable `HeroService`
|
||||
|
||||
`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.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts"
|
||||
header="src/app/hero.service.ts (Observable imports)" region="import-observable">
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts (Observable imports)" region="import-observable">
|
||||
</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.
|
||||
|
||||
|
@ -212,7 +207,7 @@ In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get<Hero[]>()`
|
|||
|
||||
</div>
|
||||
|
||||
### Subscribe in _HeroesComponent_
|
||||
### Subscribe in `HeroesComponent`
|
||||
|
||||
The `HeroService.getHeroes` method used to return a `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.
|
||||
|
||||
The new version waits for the `Observable` to emit the array of heroes—
|
||||
which could happen now or several minutes from now.
|
||||
Then `subscribe` passes the emitted array to the callback,
|
||||
The new version waits for the `Observable` to emit the array of heroes—which
|
||||
could happen now or several minutes from now.
|
||||
The `subscribe()` method passes the emitted array to the callback,
|
||||
which sets the component's `heroes` property.
|
||||
|
||||
This asynchronous approach _will work_ when
|
||||
|
@ -252,14 +247,14 @@ the `HeroService` requests heroes from the server.
|
|||
|
||||
## 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.
|
||||
* create an injectable, app-wide `MessageService` for sending messages to be displayed
|
||||
* inject `MessageService` into the `HeroService`
|
||||
* display a message when `HeroService` fetches heroes successfully.
|
||||
* adding a `MessagesComponent` that displays app messages at the bottom of the screen
|
||||
* creating an injectable, app-wide `MessageService` for sending messages to be displayed
|
||||
* injecting `MessageService` into the `HeroService`
|
||||
* displaying a message when `HeroService` fetches heroes successfully
|
||||
|
||||
### Create _MessagesComponent_
|
||||
### Create `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`.
|
||||
|
||||
Modify the `AppComponent` template to display the generated `MessagesComponent`
|
||||
Modify the `AppComponent` template to display the generated `MessagesComponent`.
|
||||
|
||||
<code-example
|
||||
header = "/src/app/app.component.html"
|
||||
header = "src/app/app.component.html"
|
||||
path="toh-pt4/src/app/app.component.html">
|
||||
</code-example>
|
||||
|
||||
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`.
|
||||
|
||||
|
@ -288,9 +283,7 @@ Use the CLI to create the `MessageService` in `src/app`.
|
|||
|
||||
Open `MessageService` and replace its contents with the following.
|
||||
|
||||
<code-example
|
||||
header = "/src/app/message.service.ts"
|
||||
path="toh-pt4/src/app/message.service.ts">
|
||||
<code-example header = "src/app/message.service.ts" path="toh-pt4/src/app/message.service.ts">
|
||||
</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.
|
||||
|
@ -298,10 +291,10 @@ The service exposes its cache of `messages` and two methods: one to `add()` a me
|
|||
{@a inject-message-service}
|
||||
### Inject it into the `HeroService`
|
||||
|
||||
Re-open the `HeroService` and import the `MessageService`.
|
||||
In `HeroService`, import the `MessageService`.
|
||||
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
|
@ -310,7 +303,7 @@ Angular will inject the singleton `MessageService` into that property
|
|||
when it creates the `HeroService`.
|
||||
|
||||
<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>
|
||||
|
||||
<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`
|
||||
|
||||
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>
|
||||
|
||||
### 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`.
|
||||
|
||||
<code-example
|
||||
header = "/src/app/messages/messages.component.ts (import MessageService)"
|
||||
path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
|
||||
<code-example header="src/app/messages/messages.component.ts (import MessageService)" path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
|
||||
</code-example>
|
||||
|
||||
Modify the constructor with a parameter that declares a **public** `messageService` property.
|
||||
Angular will inject the singleton `MessageService` into that property
|
||||
when it creates the `MessagesComponent`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt4/src/app/messages/messages.component.ts" region="ctor">
|
||||
<code-example path="toh-pt4/src/app/messages/messages.component.ts" header="src/app/messages/messages.component.ts" region="ctor">
|
||||
</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">
|
||||
|
||||
|
@ -355,7 +345,7 @@ Angular only binds to _public_ component properties.
|
|||
|
||||
</div>
|
||||
|
||||
### Bind to the _MessageService_
|
||||
### Bind to the `MessageService`
|
||||
|
||||
Replace the CLI-generated `MessagesComponent` template with the following.
|
||||
|
||||
|
|
|
@ -36,59 +36,49 @@ Use the CLI to generate it.
|
|||
|
||||
The generated file looks like this:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.0.ts"
|
||||
header="src/app/app-routing.module.ts (generated)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.0.ts" header="src/app/app-routing.module.ts (generated)">
|
||||
</code-example>
|
||||
|
||||
You generally don't declare components in a routing module so you can delete the
|
||||
`@NgModule.declarations` array and delete `CommonModule` references too.
|
||||
Replace it with the following:
|
||||
|
||||
You'll configure the router with `Routes` in the `RouterModule`
|
||||
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 path="toh-pt5/src/app/app-routing.module.1.ts" header="src/app/app-routing.module.ts (updated)">
|
||||
</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.
|
||||
|
||||
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.
|
||||
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"
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts"
|
||||
region="heroes-route">
|
||||
</code-example>
|
||||
|
||||
Once you've finished setting up, the router will match that URL to `path: 'heroes'`
|
||||
and display the `HeroesComponent`.
|
||||
A typical Angular `Route` has two properties:
|
||||
|
||||
### _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
|
||||
configure it with the `routes` in one step by calling
|
||||
`RouterModule.forRoot()` _within_ the `imports` array, like this:
|
||||
### `RouterModule.forRoot()`
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="ngmodule-imports">
|
||||
The `@NgModule` metadata initializes the router and starts it listening for browser location changes.
|
||||
|
||||
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>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
@ -99,16 +89,21 @@ configure it with the `routes` in one step by calling
|
|||
|
||||
</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.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app.component.html"
|
||||
region="outlet"
|
||||
header="src/app/app.component.html (router-outlet)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
|
||||
</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.
|
||||
|
||||
|
@ -140,22 +135,19 @@ You should see the familiar heroes master/detail view.
|
|||
|
||||
## Add a navigation link (`routerLink`)
|
||||
|
||||
Users shouldn't have to paste a route URL into the address bar.
|
||||
They should be able to click a link to navigate.
|
||||
Ideally, users should be able to click a link to navigate rather
|
||||
than pasting a route URL into the address bar.
|
||||
|
||||
Add a `<nav>` element and, within that, an anchor element that, when clicked,
|
||||
triggers navigation to the `HeroesComponent`.
|
||||
The revised `AppComponent` template looks like this:
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app.component.html"
|
||||
region="heroes"
|
||||
header="src/app/app.component.html (heroes RouterLink)">
|
||||
<code-example path="toh-pt5/src/app/app.component.html" region="heroes" header="src/app/app.component.html (heroes RouterLink)">
|
||||
</code-example>
|
||||
|
||||
A [`routerLink` attribute](#routerlink) is set to `"/heroes"`,
|
||||
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.
|
||||
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`.
|
||||
|
||||
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-pane
|
||||
|
@ -211,11 +203,11 @@ The _template_ presents a grid of hero name links.
|
|||
The _class_ is similar to the `HeroesComponent` class.
|
||||
* It defines a `heroes` array 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>
|
||||
|
||||
### 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`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="import-dashboard"
|
||||
header="src/app/app-routing.module.ts (import DashboardComponent)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-dashboard" header="src/app/app-routing.module.ts (import DashboardComponent)">
|
||||
</code-example>
|
||||
|
||||
Add a route to the `AppRoutingModule.routes` array that matches a path to the `DashboardComponent`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="dashboard-route">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="dashboard-route">
|
||||
</code-example>
|
||||
|
||||
### 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.
|
||||
The space below the `<router-outlet>` is blank.
|
||||
|
||||
To make the app navigate to the dashboard automatically, add the following
|
||||
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>
|
||||
|
||||
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`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="import-herodetail"
|
||||
header="src/app/app-routing.module.ts (import HeroDetailComponent)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-herodetail" header="src/app/app-routing.module.ts (import HeroDetailComponent)">
|
||||
</code-example>
|
||||
|
||||
Then add a _parameterized_ route to the `AppRoutingModule.routes` array that matches the path pattern to the _hero detail_ view.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="detail-route">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="detail-route">
|
||||
</code-example>
|
||||
|
||||
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.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="routes"
|
||||
header="src/app/app-routing.module.ts (all routes)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="routes" header="src/app/app-routing.module.ts (all routes)">
|
||||
</code-example>
|
||||
|
||||
### `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
|
||||
are bound to the component's `onSelect()` method.
|
||||
|
||||
<code-example
|
||||
path="toh-pt4/src/app/heroes/heroes.component.html"
|
||||
region="list"
|
||||
header="src/app/heroes/heroes.component.html (list with onSelect)">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with onSelect)">
|
||||
</code-example>
|
||||
|
||||
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
|
||||
is the same as in the dashboard template
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/heroes/heroes.component.html"
|
||||
region="list"
|
||||
header="src/app/heroes/heroes.component.html (list with links)">
|
||||
<code-example path="toh-pt5/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with links)">
|
||||
</code-example>
|
||||
|
||||
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.
|
||||
Here's the class after pruning away the dead code.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/heroes/heroes.component.ts"
|
||||
region="class"
|
||||
header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
|
||||
<code-example path="toh-pt5/src/app/heroes/heroes.component.ts" region="class" header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Routable *HeroDetailComponent*
|
||||
## Routable `HeroDetailComponent`
|
||||
|
||||
Previously, the parent `HeroesComponent` set the `HeroDetailComponent.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.
|
||||
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
|
||||
* Acquire the hero with that `id` from the server via the `HeroService`
|
||||
|
||||
Add the following imports:
|
||||
|
||||
<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">
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
{@a hero-detail-ctor}
|
||||
|
@ -409,27 +377,25 @@ Add the following imports:
|
|||
Inject the `ActivatedRoute`, `HeroService`, and `Location` services
|
||||
into the constructor, saving their values in private fields:
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ctor">
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
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.
|
||||
The _"id"_ parameter is the `id` of the hero to display.
|
||||
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 [`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.
|
||||
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)
|
||||
call `getHero()` and define it as follows.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
|
||||
<code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" header="src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
|
||||
</code-example>
|
||||
|
||||
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()`
|
||||
|
||||
Open `HeroService` and add this `getHero()` method
|
||||
Open `HeroService` and add the following `getHero()` method with the `id` after the `getHeroes()` method:
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero.service.ts"
|
||||
region="getHero"
|
||||
header="src/app/hero.service.ts (getHero)">
|
||||
<code-example path="toh-pt5/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts (getHero)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
Note the backticks ( ` ) that
|
||||
define a JavaScript
|
||||
Note the backticks ( ` ) that define a JavaScript
|
||||
[_template literal_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for embedding the `id`.
|
||||
</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
|
||||
to the component's `goBack()` method.
|
||||
|
||||
<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)">
|
||||
<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)">
|
||||
</code-example>
|
||||
|
||||
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,
|
||||
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
|
||||
|
||||
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
|
||||
|
||||
{@a approutingmodule}
|
||||
{@a appmodule}
|
||||
#### _AppRoutingModule_, _AppModule_, and _HeroService_
|
||||
#### `AppRoutingModule`, `AppModule`, and `HeroService`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -535,7 +492,7 @@ Here are the code files discussed on this page and your app should look like thi
|
|||
</code-tabs>
|
||||
|
||||
{@a appcomponent}
|
||||
#### _AppComponent_
|
||||
#### `AppComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -550,7 +507,7 @@ Here are the code files discussed on this page and your app should look like thi
|
|||
</code-tabs>
|
||||
|
||||
{@a dashboardcomponent}
|
||||
#### _DashboardComponent_
|
||||
#### `DashboardComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -567,7 +524,7 @@ Here are the code files discussed on this page and your app should look like thi
|
|||
</code-tabs>
|
||||
|
||||
{@a heroescomponent}
|
||||
#### _HeroesComponent_
|
||||
#### `HeroesComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -586,7 +543,7 @@ Here are the code files discussed on this page and your app should look like thi
|
|||
</code-tabs>
|
||||
|
||||
{@a herodetailcomponent}
|
||||
#### _HeroDetailComponent_
|
||||
#### `HeroDetailComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
|
|
@ -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.
|
||||
|
||||
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`
|
||||
* import the `HttpClientModule` symbol from `@angular/common/http`
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/app.module.ts"
|
||||
region="import-http-client"
|
||||
header="src/app/app.module.ts (Http Client import)">
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-http-client" header="src/app/app.module.ts (HttpClientModule import)">
|
||||
</code-example>
|
||||
|
||||
Next, still in the `AppModule`, add `HttpClient` to the `imports` array:
|
||||
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-httpclientmodule" header="src/app/app.module.ts (imports array excerpt)">
|
||||
</code-example>
|
||||
|
||||
* add it to the `@NgModule.imports` array
|
||||
|
||||
## Simulate a data server
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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,
|
||||
applying them to an in-memory data store, and returning simulated responses.
|
||||
|
||||
This facility is a great convenience for the tutorial.
|
||||
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.
|
||||
By using the In-memory Web API, you won't have to set up a server to learn about `HttpClient`.
|
||||
|
||||
<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 _coding along_ with this tutorial, stay here and add the *In-memory Web API* now.
|
||||
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.
|
||||
|
||||
</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">
|
||||
npm install angular-in-memory-web-api --save
|
||||
</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 language="sh" class="code-shell">
|
||||
ng generate service InMemoryData
|
||||
<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>
|
||||
|
||||
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>
|
||||
|
||||
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—
|
||||
_after importing the `HttpClientModule`_,
|
||||
—while configuring it with the `InMemoryDataService`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/app.module.ts"
|
||||
region="in-mem-web-api-imports">
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
||||
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}
|
||||
## Heroes and HTTP
|
||||
|
||||
Import some HTTP symbols that you'll need:
|
||||
In the `HeroService`, import `HttpClient` and `HttpHeaders`:
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="import-httpclient"
|
||||
header="src/app/hero.service.ts (import HTTP symbols)">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="import-httpclient" header="src/app/hero.service.ts (import HTTP symbols)">
|
||||
</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
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="ctor" >
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor" >
|
||||
</code-example>
|
||||
|
||||
Keep injecting the `MessageService`. You'll call it so frequently that
|
||||
you'll wrap it in a private `log()` method.
|
||||
Notice that you keep injecting the `MessageService` but since you'll call it so frequently, wrap it in a private `log()` method:
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="log" >
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="log" >
|
||||
</code-example>
|
||||
|
||||
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,
|
||||
and `collectionName` is the heroes data object in the `in-memory-data-service.ts`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="heroesUrl" >
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="heroesUrl" >
|
||||
</code-example>
|
||||
|
||||
### Get heroes with _HttpClient_
|
||||
### Get heroes with `HttpClient`
|
||||
|
||||
The current `HeroService.getHeroes()`
|
||||
uses the RxJS `of()` function to return an array of mock heroes
|
||||
as an `Observable<Hero[]>`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt4/src/app/hero.service.ts"
|
||||
region="getHeroes-1"
|
||||
header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1" header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
|
||||
</code-example>
|
||||
|
||||
Convert that method to use `HttpClient`
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes-1">
|
||||
Convert that method to use `HttpClient` as follows:
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1">
|
||||
</code-example>
|
||||
|
||||
Refresh the browser. The hero data should successfully load from the
|
||||
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[]>`.
|
||||
|
||||
### Http methods return one value
|
||||
### `HttpClient` methods return one value
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
</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.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="import-rxjs-operators">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="import-rxjs-operators">
|
||||
</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.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes-2" >
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHeroes-2" header="src/app/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
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
|
||||
innocuous result so that the application keeps working.
|
||||
|
||||
#### _handleError_
|
||||
#### `handleError`
|
||||
|
||||
The following `handleError()` will be shared by many `HeroService` methods
|
||||
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.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="handleError">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="handleError">
|
||||
</code-example>
|
||||
|
||||
After reporting the error to console, the handler constructs
|
||||
a user friendly message and returns a safe value to the app so it can keep working.
|
||||
After reporting the error to the console, the handler constructs
|
||||
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,
|
||||
`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
|
||||
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,
|
||||
which _looks_ at the observable values, does _something_ with those values,
|
||||
They'll do that with the RxJS `tap()` operator,
|
||||
which looks at the observable values, does something with those values,
|
||||
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
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes" >
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes" >
|
||||
</code-example>
|
||||
|
||||
### 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
|
||||
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>
|
||||
|
||||
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.
|
||||
* 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()` constructs a request URL with the desired hero's id.
|
||||
* The server should respond with a single hero rather than an array of heroes.
|
||||
* `getHero()` returns an `Observable<Hero>` ("_an observable of Hero objects_")
|
||||
rather than an observable of hero _arrays_ .
|
||||
|
||||
## 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.
|
||||
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>
|
||||
|
||||
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.
|
||||
|
||||
<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
|
||||
`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
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="updateHero"
|
||||
header="src/app/hero.service.ts (update)">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="updateHero" header="src/app/hero.service.ts (update)">
|
||||
</code-example>
|
||||
|
||||
The `HttpClient.put()` method takes three parameters
|
||||
The `HttpClient.put()` method takes three parameters:
|
||||
* the URL
|
||||
* the data to update (the modified hero in this case)
|
||||
* 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 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
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="http-options"
|
||||
header="src/app/hero.service.ts">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="http-options" header="src/app/hero.service.ts">
|
||||
</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.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
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>
|
||||
|
||||
In response to a click event, call the component's click handler and then
|
||||
clear the input field so that it's ready for another name.
|
||||
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. 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>
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
You'll write `HeroService.addHero` in the next section.
|
||||
|
||||
#### Add _HeroService.addHero()_
|
||||
|
||||
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>
|
||||
|
||||
`HeroService.addHero()` differs from `updateHero` in two ways.
|
||||
`addHero()` differs from `updateHero()` in two ways:
|
||||
|
||||
* it calls `HttpClient.post()` instead of `put()`.
|
||||
* it expects the server to generate an id for the new hero,
|
||||
* It calls `HttpClient.post()` instead of `put()`.
|
||||
* It expects the server to generate an id for the new hero,
|
||||
which it returns in the `Observable<Hero>` to the caller.
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
|
@ -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
|
||||
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>
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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">
|
||||
|
||||
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!
|
||||
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.
|
||||
|
||||
Confirm this for yourself by temporarily removing the `subscribe()`,
|
||||
clicking "Dashboard", then clicking "Heroes".
|
||||
|
@ -392,18 +350,16 @@ There's really nothing for the component to do with the `Observable` returned by
|
|||
|
||||
</div>
|
||||
|
||||
#### Add _HeroService.deleteHero()_
|
||||
|
||||
Add a `deleteHero()` method to `HeroService` like this.
|
||||
Next, 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>
|
||||
|
||||
Note that
|
||||
Note the following key points:
|
||||
|
||||
* it calls `HttpClient.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 still send the `httpOptions`.
|
||||
* `deleteHero()` calls `HttpClient.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 still send the `httpOptions`.
|
||||
|
||||
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
|
||||
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,
|
||||
you'll make repeated HTTP requests for heroes filtered by that name.
|
||||
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
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="searchHeroes"
|
||||
header="src/app/hero.service.ts">
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="searchHeroes" header="src/app/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
The method returns immediately with an empty array if there is no search term.
|
||||
The rest of it closely resembles `getHeroes()`.
|
||||
The only significant difference is the URL,
|
||||
which includes a query string with the search term.
|
||||
The rest of it closely resembles `getHeroes()`, the only significant difference being
|
||||
the URL, which includes a query string with the search term.
|
||||
|
||||
### Add search to the Dashboard
|
||||
|
||||
Open the `DashboardComponent` _template_ and
|
||||
Add the hero search element, `<app-hero-search>`, to the bottom of the `DashboardComponent` template.
|
||||
Open the `DashboardComponent` template and
|
||||
add the hero search element, `<app-hero-search>`, to the bottom of the markup.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
|
||||
<code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
|
||||
|
||||
Unfortunately, adding this element breaks the app.
|
||||
Angular can't find a component with a selector that matches `<app-hero-search>`.
|
||||
For this to work, the next step is to add 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.
|
||||
|
||||
|
@ -457,68 +406,60 @@ Create a `HeroSearchComponent` with the CLI.
|
|||
ng generate component hero-search
|
||||
</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>
|
||||
|
||||
Add private CSS styles to `hero-search.component.css`
|
||||
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()`
|
||||
method with the new search box value.
|
||||
As the user types in the search box, an input event binding calls the
|
||||
component's `search()` method with the new search box value.
|
||||
|
||||
{@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.
|
||||
|
||||
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
|
||||
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
|
||||
do so in the component class.
|
||||
|
||||
### Fix the _HeroSearchComponent_ class
|
||||
### Edit the `HeroSearchComponent` class
|
||||
|
||||
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>
|
||||
|
||||
Notice the declaration of `heroes$` as an `Observable`
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
|
||||
region="heroes-stream">
|
||||
Notice the declaration of `heroes$` as an `Observable`:
|
||||
|
||||
<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">
|
||||
</code-example>
|
||||
|
||||
You'll set it in [`ngOnInit()`](#search-pipe).
|
||||
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 also push values into that `Observable` by calling its `next(value)` method
|
||||
as the `search()` method does.
|
||||
|
||||
The `search()` method is called via an _event binding_ to the
|
||||
textbox's `input` event.
|
||||
The event binding to the textbox's `input` event calls the `search()` method.
|
||||
|
||||
<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".
|
||||
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
|
||||
|
||||
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()`,
|
||||
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
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
|
||||
region="search">
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="search">
|
||||
</code-example>
|
||||
|
||||
|
||||
Each operator works as follows:
|
||||
|
||||
* `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.
|
||||
|
||||
|
||||
* `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.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
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.
|
||||
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 inmemorydataservice}
|
||||
{@a appmodule}
|
||||
#### _HeroService_, _InMemoryDataService_, _AppModule_
|
||||
#### `HeroService`, `InMemoryDataService`, `AppModule`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -608,7 +545,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||
</code-tabs>
|
||||
|
||||
{@a heroescomponent}
|
||||
#### _HeroesComponent_
|
||||
#### `HeroesComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -626,7 +563,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||
</code-tabs>
|
||||
|
||||
{@a herodetailcomponent}
|
||||
#### _HeroDetailComponent_
|
||||
#### `HeroDetailComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -640,7 +577,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||
</code-tabs>
|
||||
|
||||
{@a dashboardcomponent}
|
||||
#### _DashboardComponent_
|
||||
#### `DashboardComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
@ -650,7 +587,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||
</code-tabs>
|
||||
|
||||
{@a herosearchcomponent}
|
||||
#### _HeroSearchComponent_
|
||||
#### `HeroSearchComponent`
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
|
|
Loading…
Reference in New Issue