2017-05-10 05:06:04 -04:00
# HTTP
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
In this tutorial, you'll add the following data persistence features with help from
Angular's `HttpClient` .
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
* The `HeroService` gets hero data with HTTP requests.
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* Users can add, edit, and delete heroes and save these changes over HTTP.
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* Users can search for heroes by name.
2017-02-22 13:09:39 -05:00
2017-03-27 11:08:53 -04:00
When you're done with this page, the app should look like this < live-example > < / live-example > .
2017-11-06 13:02:18 -05:00
## Enable HTTP services
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
To make `HttpClient` available everywhere in the app,
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
* open the root `AppModule` ,
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* import the `HttpClientModule` symbol from `@angular/common/http` ,
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* add it to the `@NgModule.imports` array.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
## Simulate a data server
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
This facility is a great convenience for the tutorial.
You won't have to set up a server to learn about `HttpClient` .
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< div class = "alert is-important" >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
**Important:** the *In-memory Web API* module has nothing to do with HTTP in Angular.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< / div >
2017-05-10 05:06:04 -04:00
2017-11-06 13:02:18 -05:00
Install the *In-memory Web API* package from _npm_
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example language = "sh" class = "code-shell" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
npm install angular-in-memory-web-api --save
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Import the `InMemoryWebApiModule` and the `InMemoryDataService` class,
which you will create in a moment.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/app.module.ts"
region="import-in-mem-stuff"
title="src/app/app.module.ts (In-memory Web API imports)">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Add the `InMemoryWebApiModule` to the `@NgModule.imports` array—
_after importing the `HttpClient` _,
— while configuring it with the `InMemoryDataService` .
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/app.module.ts"
region="in-mem-web-api-imports">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-03-27 11:08:53 -04:00
The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
2017-11-06 13:02:18 -05:00
The _Tour of Heroes_ sample creates such a class
`src/app/in-memory-data.service.ts` which has the following content:
2017-03-27 11:08:53 -04:00
2017-05-10 05:06:04 -04:00
< code-example path = "toh-pt6/src/app/in-memory-data.service.ts" region = "init" title = "src/app/in-memory-data.service.ts" linenums = "false" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-03-27 11:08:53 -04:00
This file replaces `mock-heroes.ts` , which is now safe to delete.
2017-11-06 13:02:18 -05:00
When your server is ready, detach the *In-memory Web API* , and the app's requests will go through to the server.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Now back to the `HttpClient` story.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
{@a import-heroes}
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
## Heroes and HTTP
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Import some HTTP symbols that you'll need:
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="import-httpclient"
title="src/app/hero.service.ts (import HTTP symbols)">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Inject `HttpClient` into the constructor in a private property called `http` .
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="ctor" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Keep injecting the `MessageService` . You'll call it so frequently that
you'll wrap it in private `log` method.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="log" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Define the `heroesUrl` with the address of the heroes resource on the server.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="heroesUrl" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
### Get heroes with _HttpClient_
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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"
title="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
Convert that method to use `HttpClient`
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="getHeroes-1">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-03-27 11:08:53 -04:00
Refresh the browser. The hero data should successfully load from the
2017-02-22 13:09:39 -05:00
mock server.
2017-12-09 01:47:57 -05:00
You've swapped `of` for `http.get` and the app keeps working without any other changes
2017-11-06 13:02:18 -05:00
because both functions return an `Observable<Hero[]>` .
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
### Http methods return one value
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
All `HttpClient` methods return an RxJS `Observable` of something.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
HTTP is a request/response protocol.
You make a request, it returns a single response.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
### _HttpClient.get_ returns response data
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
`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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
The shape of the JSON data is determined by the server's data API.
The _Tour of Heroes_ data API returns the hero data as an array.
2017-03-27 11:08:53 -04:00
2017-04-10 11:51:13 -04:00
< div class = "l-sub-section" >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Although not discussed here, there's an example of `map` in the `getHeroNo404()`
method included in the sample source code.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< / div >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
### Error handling
2017-03-27 11:08:53 -04:00
2018-03-07 01:35:57 -05:00
### 错误处理
2017-11-06 13:02:18 -05:00
Things go wrong, especially when you're getting data from a remote server.
The `HeroService.getHeroes()` method should catch errors and do something appropriate.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
To catch errors, you ** "pipe" the observable** result from `http.get()` through an RxJS `catchError()` operator.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Import the `catchError` symbol from `rxjs/operators` , along with some other operators you'll need later.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="import-rxjs-operators">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Now extend the observable result with the `.pipe()` method and
give it a `catchError()` operator.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="getHeroes-2" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-15 07:32:41 -05:00
The `catchError()` operator intercepts an ** `Observable` that failed**.
2017-11-06 13:02:18 -05:00
It passes the error an _error handler_ that can do what it wants with the error.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The following `handleError()` method reports the error and then returns an
innocuous result so that the application keeps working.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
#### _handleError_
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
The following `errorHandler()` will be shared by many `HeroService` methods
so it's generalized to meet their different needs.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="handleError">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Because each service method returns a different kind of `Observable` result,
`errorHandler()` takes a type parameter so it can return the safe value as the type that the app expects.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
### Tap into the _Observable_
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
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.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
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.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Here is the final version of `getHeroes` with the `tap` that logs the operation.
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="getHeroes" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
### Get hero by id
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Most web APIs support a _get by id_ request in the form `api/hero/:id`
(such as `api/hero/11` ).
Add a `HeroService.getHero()` method to make that request:
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero.service.ts" region = "getHero" title = "src/app/hero.service.ts" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
There are three significant differences from `getHeroes()` .
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
* it constructs a request URL with the desired hero's id.
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* the server should respond with a single hero rather than an array of heroes.
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* therefore, `getHero` returns an `Observable<Hero>` ("_an observable of Hero objects_")
rather than an observable of hero _arrays_ .
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
## Update heroes
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Editing 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.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
If you want changes to persist, you must write them back to
2017-02-22 13:09:39 -05:00
the server.
2017-03-27 11:08:53 -04:00
At the end of the hero detail template, add a save button with a `click` event
2017-03-30 15:04:18 -04:00
binding that invokes a new component method named `save()` .
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-detail/hero-detail.component.html" region = "save" title = "src/app/hero-detail/hero-detail.component.html (save)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-03-30 15:04:18 -04:00
Add the following `save()` method, which persists hero name changes using the hero service
2017-11-06 13:02:18 -05:00
`updateHero()` method and then navigates back to the previous view.
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-detail/hero-detail.component.ts" region = "save" title = "src/app/hero-detail/hero-detail.component.ts (save)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
#### 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.
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="updateHero"
title="src/app/hero.service.ts (update)">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
The `HttpClient.put()` method takes three parameters
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* the URL
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* the data to update (the modified hero in this case)
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* options
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id` .
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The heroes web API expects a special header in HTTP save requests.
2018-02-26 10:51:09 -05:00
That header is in the `httpOptions` constant defined in the `HeroService` .
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="http-options">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-03-31 19:57:13 -04:00
Refresh the browser, change a hero name, save your change,
2017-11-06 13:02:18 -05:00
and click the "go back" button.
The hero now appears in the list with the changed name.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
## Add a new hero
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
To add a hero, this app only needs the hero's name. You can use an `input`
2017-03-27 11:08:53 -04:00
element paired with an add button.
2017-11-06 13:02:18 -05:00
Insert the following into the `HeroesComponent` template, just after
2017-02-22 13:09:39 -05:00
the heading:
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/heroes/heroes.component.html" region = "add" title = "src/app/heroes/heroes.component.html (add)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-03-27 11:08:53 -04:00
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.
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/heroes/heroes.component.ts" region = "add" title = "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
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.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero.service.ts" region = "addHero" title = "src/app/hero.service.ts (addHero)" > < / code-example >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
`HeroService.addHero()` differs from `updateHero` in two ways.
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
* it calls `HttpClient.post()` instead of `put()` .
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* it expects the server to generates an id for the new hero,
which it returns in the `Observable<Hero>` to the caller.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Refresh the browser and add some heroes.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
## Delete a hero
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Each hero in the heroes list should have a delete button.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Add the following button element to the `HeroesComponent` template, after the hero
2017-03-30 15:04:18 -04:00
name in the repeated `<li>` element.
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/heroes/heroes.component.html" region = "delete" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The HTML for the list of heroes should look like this:
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/heroes/heroes.component.html" region = "list" title = "src/app/heroes/heroes.component.html (list of heroes)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Add the `delete()` handler to the component.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/heroes/heroes.component.ts" region = "delete" title = "src/app/heroes/heroes.component.ts (delete)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Although the component delegates hero deletion to the `HeroService` ,
it remains responsible for updating its own list of heroes.
The component's `delete()` method immediately removes the _hero-to-delete_ from that list,
anticipating that the `HeroService` will succeed on the server.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
There's really nothing for the component to do with the `Observable` returned by
`heroService.delete()` . **It must subscribe anyway** .
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
< div class = "alert is-important" >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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!
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
Confirm this for yourself by temporarily removing the `subscribe()` ,
clicking "Dashboard", then clicking "Heroes".
You'll see the full list of heroes again.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< / div >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
#### Add _HeroService.deleteHero()_
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Add a `deleteHero()` method to `HeroService` like this.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero.service.ts" region = "deleteHero" title = "src/app/hero.service.ts (delete)" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Note that
* it calls `HttpClient.delete` .
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* the URL is the heroes resource URL plus the `id` of the hero to delete
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* you don't send data as you did with `put` and `post` .
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
* you still send the `httpOptions` .
Refresh the browser and try the new delete functionality.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
## Search by name
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-05-10 05:06:04 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
#### _HeroService.searchHeroes_
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Start by adding a `searchHeroes` method to the `HeroService` .
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/hero.service.ts"
region="searchHeroes"
title="src/app/hero.service.ts">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
2017-04-12 15:53:18 -04:00
2017-11-06 13:02:18 -05:00
### Add search to the Dashboard
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Open the `DashboardComponent` _template_ and
Add the hero search element, `<app-hero-search>` , to the bottom of the `DashboardComponent` template.
2017-05-10 05:06:04 -04:00
2017-11-06 13:02:18 -05:00
< code-example
path="toh-pt6/src/app/dashboard/dashboard.component.html" title="src/app/dashboard/dashboard.component.html" linenums="false">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Unfortunately, adding this element breaks the app.
Angular can't find a component with a selector that matches `<app-hero-search>` .
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The `HeroSearchComponent` doesn't exist yet. Fix that.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
### Create _HeroSearchComponent_
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Create a `HeroSearchComponent` with the CLI.
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-example language = "sh" class = "code-shell" >
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
ng generate component hero-search
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
The CLI generates the three `HeroSearchComponent` and adds the component to the `AppModule' declarations
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Replace the generated `HeroSearchComponent` _template_ with a text box and a list of matching search results like this.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-search/hero-search.component.html" title = "src/app/hero-search/hero-search.component.html" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
Add private CSS styles to `hero-search.component.css`
as listed in the [final code review ](#herosearchcomponent ) below.
2017-03-31 19:57:13 -04:00
As the user types in the search box, a *keyup* event binding calls the component's `search()`
2017-03-27 11:08:53 -04:00
method with the new search box value.
2017-11-06 13:02:18 -05:00
{@a asyncpipe}
### _AsyncPipe_
As expected, the `*ngFor` repeats hero objects.
Look closely and you'll see that the `*ngFor` iterates over a list called `heroes$` , not `heroes` .
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-search/hero-search.component.html" region = "async" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
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
do so in the component class.
### Fix 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" title = "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">
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
< / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
You'll set it in [`ngOnInit()` ](#search-pipe ).
Before you do, focus on the definition of `searchTerms` .
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
### The _searchTerms_ RxJS subject
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
The `searchTerms` property is declared as an RxJS `Subject` .
2017-03-30 15:04:18 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-search/hero-search.component.ts" region = "searchTerms" > < / code-example >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
A `Subject` is both a source of _observable_ values and an `Observable` itself.
You can subscribe to a `Subject` as you would any `Observable` .
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
You can also push values into that `Observable` by calling its `next(value)` method
as the `search()` method does.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
The `search()` method is called via an _event binding_ to the
textbox's `keystroke` event.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-example path = "toh-pt6/src/app/hero-search/hero-search.component.html" region = "input" > < / code-example >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
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.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
{@a search-pipe}
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
### Chaining RxJS operators
Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests,
2017-03-27 11:08:53 -04:00
taxing server resources and burning through the cellular network data plan.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
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.
< code-example
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
region="search">
2018-03-03 08:06:01 -05:00
< / code-example >
2017-03-27 11:08:53 -04:00
* `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.
2017-11-06 13:02:18 -05:00
2017-03-31 07:23:16 -04:00
* `distinctUntilChanged` ensures that a request is sent only if the filter text changed.
2017-11-06 13:02:18 -05:00
2017-03-31 07:23:16 -04:00
* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged` .
2017-03-27 11:08:53 -04:00
It cancels and discards previous search observables, returning only the latest search service observable.
2017-04-10 11:51:13 -04:00
< div class = "l-sub-section" >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
With the [switchMap operator ](http://www.learnrxjs.io/operators/transformation/switchmap.html ),
every qualifying key event can trigger an `HttpClient.get()` method call.
2017-05-10 05:06:04 -04:00
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
and they may not return in the order sent.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
`switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call.
2017-05-10 05:06:04 -04:00
Results from prior calls are canceled and discarded.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Note that _canceling_ a previous `searchHeroes()` _Observable_
2017-05-10 05:06:04 -04:00
doesn't actually abort a pending HTTP request.
2017-11-06 13:02:18 -05:00
Unwanted results are simply discarded before they reach your application code.
2017-03-27 11:08:53 -04:00
2017-04-10 11:51:13 -04:00
< / div >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Remember that the component _class_ does not subscribe to the `heroes$` _observable_ .
That's the job of the [`AsyncPipe` ](#asyncpipe ) in the template.
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
#### Try it
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Run the app again. In the *Dashboard* , enter some text in the search box.
If you enter characters that match any existing hero names, you'll see something like this.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< figure >
< img src = 'generated/images/guide/toh/toh-hero-search.png' alt = "Hero Search Component" >
< / figure >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
## Final code review
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
Your app should look like this < live-example > < / live-example > .
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
Here are the code files discussed on this page (all in the `src/app/` folder).
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
{@a heroservice}
2018-03-06 22:25:56 -05:00
2017-11-06 13:02:18 -05:00
{@a inmemorydataservice}
2018-03-06 22:25:56 -05:00
2017-11-06 13:02:18 -05:00
{@a appmodule}
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
#### _HeroService_, _InMemoryDataService_, _AppModule_
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-tabs >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< code-pane
title="hero.service.ts"
path="toh-pt6/src/app/hero.service.ts">
< / code-pane >
< code-pane
title="in-memory-data.service.ts"
path="toh-pt6/src/app/in-memory-data.service.ts">
< / code-pane >
< code-pane
title="app.module.ts"
path="toh-pt6/src/app/app.module.ts">
< / code-pane >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< / code-tabs >
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
{@a heroescomponent}
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
#### _HeroesComponent_
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
< code-tabs >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< code-pane
title="heroes/heroes.component.html"
path="toh-pt6/src/app/heroes/heroes.component.html">
< / code-pane >
< code-pane
title="heroes/heroes.component.ts"
path="toh-pt6/src/app/heroes/heroes.component.ts">
< / code-pane >
< code-pane
title="heroes/heroes.component.css"
path="toh-pt6/src/app/heroes/heroes.component.css">
< / code-pane >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< / code-tabs >
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
{@a herodetailcomponent}
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
#### _HeroDetailComponent_
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
< code-tabs >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< code-pane
title="hero-detail/hero-detail.component.html"
path="toh-pt6/src/app/hero-detail/hero-detail.component.html">
< / code-pane >
< code-pane
title="hero-detail/hero-detail.component.ts"
path="toh-pt6/src/app/hero-detail/hero-detail.component.ts">
< / code-pane >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< / code-tabs >
2017-03-31 19:57:13 -04:00
2017-11-06 13:02:18 -05:00
{@a herosearchcomponent}
2018-03-03 08:06:01 -05:00
2017-11-06 13:02:18 -05:00
#### _HeroSearchComponent_
2017-02-22 13:09:39 -05:00
2017-11-06 13:02:18 -05:00
< code-tabs >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< code-pane
title="hero-search/hero-search.component.html"
path="toh-pt6/src/app/hero-search/hero-search.component.html">
< / code-pane >
< code-pane
title="hero-search/hero-search.component.ts"
path="toh-pt6/src/app/hero-search/hero-search.component.ts">
< / code-pane >
< code-pane
title="hero-search/hero-search.component.css"
path="toh-pt6/src/app/hero-search/hero-search.component.css">
< / code-pane >
2018-03-06 23:26:05 -05:00
2017-11-06 13:02:18 -05:00
< / code-tabs >
2017-02-22 13:09:39 -05:00
2017-09-27 16:45:47 -04:00
## Summary
2017-02-22 13:09:39 -05:00
2018-03-03 08:06:01 -05:00
## 小结
2017-03-27 11:08:53 -04:00
You're at the end of your journey, and you've accomplished a lot.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You added the necessary dependencies to use HTTP in the app.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You refactored `HeroService` to load heroes from a web API.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You extended `HeroService` to support `post()` , `put()` , and `delete()` methods.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You updated the components to allow adding, editing, and deleting of heroes.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You configured an in-memory web API.
2018-03-03 08:06:01 -05:00
2017-03-31 19:57:13 -04:00
* You learned how to use Observables.
2017-03-27 11:08:53 -04:00
2017-11-06 13:02:18 -05:00
This concludes the "Tour of Heroes" tutorial.
2017-05-04 17:30:29 -04:00
You're ready to learn more about Angular development in the fundamentals section,
starting with the [Architecture ](guide/architecture "Architecture" ) guide.