docs: translate tutorial/toh-pt6.md
- Se agrego la versión en ingles .en.md - Se Probo previamente
This commit is contained in:
parent
6062e658db
commit
1e2e9201b2
621
aio/content/tutorial/toh-pt6.en.md
Normal file
621
aio/content/tutorial/toh-pt6.en.md
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
# Get data from a server
|
||||||
|
|
||||||
|
In this tutorial, you'll add the following data persistence features with help from
|
||||||
|
Angular's `HttpClient`.
|
||||||
|
|
||||||
|
* The `HeroService` gets hero data with HTTP requests.
|
||||||
|
* Users can add, edit, and delete heroes and save these changes over HTTP.
|
||||||
|
* Users can search for heroes by name.
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
For the sample app that this page describes, see the <live-example></live-example>.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Enable HTTP services
|
||||||
|
|
||||||
|
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
|
||||||
|
|
||||||
|
Make `HttpClient` available everywhere in the app in two steps. First, add it to the root `AppModule` by importing it:
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
After the `HttpClientModule`, add the `HttpClientInMemoryWebApiModule`
|
||||||
|
to the `AppModule` `imports` array and configure it with the `InMemoryDataService`.
|
||||||
|
|
||||||
|
<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"></code-example>
|
||||||
|
|
||||||
|
The `in-memory-data.service.ts` file will take over the function of `mock-heroes.ts`.
|
||||||
|
However, don't delete `mock-heroes.ts` yet, as you still need it for a few more steps of this tutorial.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
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" header="src/app/hero.service.ts" region="ctor" >
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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" 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" header="src/app/hero.service.ts" region="heroesUrl" >
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
### 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>
|
||||||
|
|
||||||
|
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
|
||||||
|
because both functions return an `Observable<Hero[]>`.
|
||||||
|
|
||||||
|
### `HttpClient` methods return one value
|
||||||
|
|
||||||
|
All `HttpClient` methods return an RxJS `Observable` of something.
|
||||||
|
|
||||||
|
HTTP is a request/response protocol.
|
||||||
|
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[]>`; 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 the body of the response as an untyped JSON object by default.
|
||||||
|
Applying the optional type specifier, `<Hero[]>` , adds TypeScript capabilities, which reduce errors during compile time.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Although not discussed here, there's an example of `map()` in the `getHeroNo404()`
|
||||||
|
method included in the sample source code.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
Things go wrong, especially when you're getting data from a remote server.
|
||||||
|
The `HeroService.getHeroes()` method should catch errors and do something appropriate.
|
||||||
|
|
||||||
|
To catch errors, you **"pipe" the observable** result from `http.get()` through an RxJS `catchError()` operator.
|
||||||
|
|
||||||
|
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" header="src/app/hero.service.ts" region="import-rxjs-operators">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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" 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.
|
||||||
|
|
||||||
|
The following `handleError()` method reports the error and then returns an
|
||||||
|
innocuous result so that the application keeps working.
|
||||||
|
|
||||||
|
#### `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
|
||||||
|
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" header="src/app/hero.service.ts" region="handleError">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The `HeroService` methods will **tap** into the flow of observable values
|
||||||
|
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,
|
||||||
|
and passes them along.
|
||||||
|
The `tap()` call back doesn't touch the values themselves.
|
||||||
|
|
||||||
|
Here is the final version of `getHeroes()` with the `tap()` that logs the operation.
|
||||||
|
|
||||||
|
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes" >
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
### Get hero by id
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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()`:
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you want changes to persist, you must write them back to
|
||||||
|
the server.
|
||||||
|
|
||||||
|
At the end of the hero detail template, add a save button with a `click` event
|
||||||
|
binding that invokes a new component method named `save()`.
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
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()`
|
||||||
|
|
||||||
|
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. 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>
|
||||||
|
|
||||||
|
The `HttpClient.put()` method takes three parameters:
|
||||||
|
* the URL
|
||||||
|
* the data to update (the modified hero in this case)
|
||||||
|
* options
|
||||||
|
|
||||||
|
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`. 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>
|
||||||
|
|
||||||
|
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>`
|
||||||
|
element paired with an add button.
|
||||||
|
|
||||||
|
Insert the following into the `HeroesComponent` template, just after
|
||||||
|
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, `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
|
||||||
|
receives the new hero and pushes it into to the `heroes` list for display.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
`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,
|
||||||
|
which it returns in the `Observable<Hero>` to the caller.
|
||||||
|
|
||||||
|
Refresh the browser and add some heroes.
|
||||||
|
|
||||||
|
## Delete a hero
|
||||||
|
|
||||||
|
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" header="src/app/heroes/heroes.component.html" region="delete"></code-example>
|
||||||
|
|
||||||
|
The HTML for the list of heroes should look like this:
|
||||||
|
|
||||||
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list of heroes)"></code-example>
|
||||||
|
|
||||||
|
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 class.
|
||||||
|
|
||||||
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
There's really nothing for the component to do with the `Observable` returned by
|
||||||
|
`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.
|
||||||
|
|
||||||
|
Confirm this for yourself by temporarily removing the `subscribe()`,
|
||||||
|
clicking "Dashboard", then clicking "Heroes".
|
||||||
|
You'll see the full list of heroes again.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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 the following key points:
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
## Search by name
|
||||||
|
|
||||||
|
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.
|
||||||
|
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()`
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
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 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 markup.
|
||||||
|
|
||||||
|
<code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html"></code-example>
|
||||||
|
|
||||||
|
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
|
||||||
|
|
||||||
|
For this to work, the next step is to add a component with a selector that matches `<app-hero-search>`.
|
||||||
|
|
||||||
|
|
||||||
|
### Create `HeroSearchComponent`
|
||||||
|
|
||||||
|
Create a `HeroSearchComponent` with the CLI.
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
ng generate component hero-search
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
{@a asyncpipe}
|
||||||
|
|
||||||
|
### `AsyncPipe`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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" 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` property is an RxJS `Subject`.
|
||||||
|
|
||||||
|
<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.
|
||||||
|
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 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" 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.
|
||||||
|
|
||||||
|
{@a search-pipe}
|
||||||
|
|
||||||
|
### 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 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 a closer look at the code.
|
||||||
|
|
||||||
|
<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()`.
|
||||||
|
It cancels and discards previous search observables, returning only the latest search service observable.
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html),
|
||||||
|
every qualifying key event can trigger an `HttpClient.get()` method call.
|
||||||
|
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
|
||||||
|
and they may not return in the order sent.
|
||||||
|
|
||||||
|
`switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call.
|
||||||
|
Results from prior calls are canceled and discarded.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Remember that the component _class_ does not subscribe to the `heroes$` _observable_.
|
||||||
|
That's the job of the [`AsyncPipe`](#asyncpipe) in the template.
|
||||||
|
|
||||||
|
#### Try it
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<div class="lightbox">
|
||||||
|
<img src='generated/images/guide/toh/toh-hero-search.png' alt="Hero Search Component">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Final code review
|
||||||
|
|
||||||
|
Here are the code files discussed on this page (all in the `src/app/` folder).
|
||||||
|
|
||||||
|
{@a heroservice}
|
||||||
|
{@a inmemorydataservice}
|
||||||
|
{@a appmodule}
|
||||||
|
#### `HeroService`, `InMemoryDataService`, `AppModule`
|
||||||
|
|
||||||
|
<code-tabs>
|
||||||
|
<code-pane
|
||||||
|
header="hero.service.ts"
|
||||||
|
path="toh-pt6/src/app/hero.service.ts">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="in-memory-data.service.ts"
|
||||||
|
path="toh-pt6/src/app/in-memory-data.service.ts">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="app.module.ts"
|
||||||
|
path="toh-pt6/src/app/app.module.ts">
|
||||||
|
</code-pane>
|
||||||
|
</code-tabs>
|
||||||
|
|
||||||
|
{@a heroescomponent}
|
||||||
|
#### `HeroesComponent`
|
||||||
|
|
||||||
|
<code-tabs>
|
||||||
|
<code-pane
|
||||||
|
header="heroes/heroes.component.html"
|
||||||
|
path="toh-pt6/src/app/heroes/heroes.component.html">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="heroes/heroes.component.ts"
|
||||||
|
path="toh-pt6/src/app/heroes/heroes.component.ts">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="heroes/heroes.component.css"
|
||||||
|
path="toh-pt6/src/app/heroes/heroes.component.css">
|
||||||
|
</code-pane>
|
||||||
|
</code-tabs>
|
||||||
|
|
||||||
|
{@a herodetailcomponent}
|
||||||
|
#### `HeroDetailComponent`
|
||||||
|
|
||||||
|
<code-tabs>
|
||||||
|
<code-pane
|
||||||
|
header="hero-detail/hero-detail.component.html"
|
||||||
|
path="toh-pt6/src/app/hero-detail/hero-detail.component.html">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="hero-detail/hero-detail.component.ts"
|
||||||
|
path="toh-pt6/src/app/hero-detail/hero-detail.component.ts">
|
||||||
|
</code-pane>
|
||||||
|
</code-tabs>
|
||||||
|
|
||||||
|
{@a dashboardcomponent}
|
||||||
|
#### `DashboardComponent`
|
||||||
|
|
||||||
|
<code-tabs>
|
||||||
|
<code-pane
|
||||||
|
header="src/app/dashboard/dashboard.component.html"
|
||||||
|
path="toh-pt6/src/app/dashboard/dashboard.component.html">
|
||||||
|
</code-pane>
|
||||||
|
</code-tabs>
|
||||||
|
|
||||||
|
{@a herosearchcomponent}
|
||||||
|
#### `HeroSearchComponent`
|
||||||
|
|
||||||
|
<code-tabs>
|
||||||
|
<code-pane
|
||||||
|
header="hero-search/hero-search.component.html"
|
||||||
|
path="toh-pt6/src/app/hero-search/hero-search.component.html">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="hero-search/hero-search.component.ts"
|
||||||
|
path="toh-pt6/src/app/hero-search/hero-search.component.ts">
|
||||||
|
</code-pane>
|
||||||
|
<code-pane
|
||||||
|
header="hero-search/hero-search.component.css"
|
||||||
|
path="toh-pt6/src/app/hero-search/hero-search.component.css">
|
||||||
|
</code-pane>
|
||||||
|
</code-tabs>
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
You're at the end of your journey, and you've accomplished a lot.
|
||||||
|
* You added the necessary dependencies to use HTTP in the app.
|
||||||
|
* You refactored `HeroService` to load heroes from a web API.
|
||||||
|
* You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
|
||||||
|
* You updated the components to allow adding, editing, and deleting of heroes.
|
||||||
|
* You configured an in-memory web API.
|
||||||
|
* You learned how to use observables.
|
||||||
|
|
||||||
|
This concludes the "Tour of Heroes" tutorial.
|
||||||
|
You're ready to learn more about Angular development in the fundamentals section,
|
||||||
|
starting with the [Architecture](guide/architecture "Architecture") guide.
|
@ -1,467 +1,463 @@
|
|||||||
# Get data from a server
|
# Obtener datos desde un servidor
|
||||||
|
|
||||||
In this tutorial, you'll add the following data persistence features with help from
|
En este tutorial, agregará las siguientes características de persistencia de datos con la ayuda de
|
||||||
Angular's `HttpClient`.
|
Angular `HttpClient`.
|
||||||
|
|
||||||
* The `HeroService` gets hero data with HTTP requests.
|
* El `HeroService` obtiene datos del héroe con solicitudes HTTP.
|
||||||
* Users can add, edit, and delete heroes and save these changes over HTTP.
|
* Los usuarios pueden agregar, editar y eliminar héroes y guardar estos cambios a través de HTTP.
|
||||||
* Users can search for heroes by name.
|
* Los usuarios pueden buscar héroes por nombre.
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
For the sample app that this page describes, see the <live-example></live-example>.
|
Para ver la aplicación de ejemplo que describe esta página, consulte el<live-example></live-example>.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Enable HTTP services
|
## Habilitar servicios HTTP
|
||||||
|
|
||||||
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
|
`HttpClient` es el mecanismo de Angular para comunicarse con un servidor remoto a través de HTTP.
|
||||||
|
|
||||||
Make `HttpClient` available everywhere in the app in two steps. First, add it to the root `AppModule` by importing it:
|
Haga que `HttpClient` esté disponible en todas partes de la aplicación en dos pasos. Primero, agréguelo a la raíz `AppModule` importándolo:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-http-client" header="src/app/app.module.ts (HttpClientModule 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>
|
</code-example>
|
||||||
|
|
||||||
Next, still in the `AppModule`, add `HttpClient` to the `imports` array:
|
A continuación, aún en el `AppModule`, agregue` HttpClient` a el arreglo `imports`:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-httpclientmodule" header="src/app/app.module.ts (imports array excerpt)">
|
<code-example path="toh-pt6/src/app/app.module.ts" region="import-httpclientmodule" header="src/app/app.module.ts (imports array excerpt)">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
## Simulate a data server
|
## Simular un servidor de datos
|
||||||
|
|
||||||
This tutorial sample mimics communication with a remote data server by using the
|
Este ejemplo de tutorial imita la comunicación con un servidor de datos remoto mediante el uso de el modulo
|
||||||
[In-memory Web API](https://github.com/angular/in-memory-web-api "In-memory Web API") module.
|
[API web en memoria](https://github.com/angular/in-memory-web-api "API web en memoria").
|
||||||
|
|
||||||
After installing the module, the app will make requests to and receive responses from the `HttpClient`
|
Después de instalar el módulo, la aplicación realizará solicitudes y recibirá respuestas del `HttpClient`
|
||||||
without knowing that the *In-memory Web API* is intercepting those requests,
|
sin saber que la *API web en memoria* está interceptando esas solicitudes,
|
||||||
applying them to an in-memory data store, and returning simulated responses.
|
aplicándolos a un almacén de datos en memoria y devolviendo respuestas simuladas.
|
||||||
|
|
||||||
By using the In-memory Web API, you won't have to set up a server to learn about `HttpClient`.
|
Al utilizar la API web en memoria, no tendrá que configurar un servidor para obtener información sobre `HttpClient`.
|
||||||
|
|
||||||
<div class="alert is-important">
|
<div class="alert is-important">
|
||||||
|
|
||||||
**Important:** the In-memory Web API module has nothing to do with HTTP in Angular.
|
**Importante:** el módulo API web en memoria no tiene nada que ver con HTTP en Angular.
|
||||||
|
|
||||||
If you're just reading this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step.
|
Si solo está leyendo este tutorial para aprender sobre `HttpClient`, puede [omitir](# import-heroes) este paso.
|
||||||
If you're coding along with this tutorial, stay here and add the In-memory Web API now.
|
Si está codificando junto con este tutorial, quédese aquí y agregue la API web en memoria ahora.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Install the In-memory Web API package from npm with the following command:
|
Instale el paquete de API web en memoria desde npm con el siguiente comando:
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
<code-example language="sh" class="code-shell">
|
||||||
npm install angular-in-memory-web-api --save
|
npm install angular-in-memory-web-api --save
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
In the `AppModule`, import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class,
|
En el `AppModule`, importe el `HttpClientInMemoryWebApiModule` y la clase `InMemoryDataService`,
|
||||||
which you will create in a moment.
|
que crearás en un momento.
|
||||||
|
|
||||||
<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 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>
|
</code-example>
|
||||||
|
|
||||||
After the `HttpClientModule`, add the `HttpClientInMemoryWebApiModule`
|
Después del `HttpClientModule`, agregue el `HttpClientInMemoryWebApiModule`
|
||||||
to the `AppModule` `imports` array and configure it with the `InMemoryDataService`.
|
a el arreglo de `AppModule` justo en `imports` y configúrelo con el `InMemoryDataService`.
|
||||||
|
|
||||||
<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 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>
|
</code-example>
|
||||||
|
|
||||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
El método de configuración `forRoot ()` toma una clase `InMemoryDataService`
|
||||||
that primes the in-memory database.
|
eso prepara la base de datos en memoria.
|
||||||
|
|
||||||
Generate the class `src/app/in-memory-data.service.ts` with the following command:
|
Genere la clase `src/app/in-memory-data.service.ts` con el siguiente comando:
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
<code-example language="sh" class="code-shell">
|
||||||
ng generate service InMemoryData
|
ng generate service InMemoryData
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Replace the default contents of `in-memory-data.service.ts` with the following:
|
Reemplace el contenido predeterminado de `in-memory-data.service.ts` con lo siguiente:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts"></code-example>
|
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts"></code-example>
|
||||||
|
|
||||||
The `in-memory-data.service.ts` file will take over the function of `mock-heroes.ts`.
|
El archivo `in-memory-data.service.ts` asumirá la función de `mock-heroes.ts`.
|
||||||
However, don't delete `mock-heroes.ts` yet, as you still need it for a few more steps of this tutorial.
|
Sin embargo, no elimine `mock-heroes.ts` todavía, ya que aún lo necesita para algunos pasos más de este tutorial.
|
||||||
|
|
||||||
When the server is ready, you'll detach the In-memory Web API, and the app's requests will go through to the server.
|
Cuando el servidor esté listo, desconectará la API web en memoria y las solicitudes de la aplicación se enviarán al servidor.
|
||||||
|
|
||||||
|
|
||||||
{@a import-heroes}
|
{@a import-heroes}
|
||||||
## Heroes and HTTP
|
## Heroes and HTTP
|
||||||
|
|
||||||
In the `HeroService`, import `HttpClient` and `HttpHeaders`:
|
En el `HeroService`, importe` HttpClient` y `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>
|
</code-example>
|
||||||
|
|
||||||
Still in the `HeroService`, inject `HttpClient` into the constructor in a private property called `http`.
|
Aún en el `HeroService`, inyecte `HttpClient` en el constructor en una propiedad privada llamada `http`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
Notice that you keep injecting the `MessageService` but since you'll call it so frequently, wrap it in a private `log()` method:
|
Observe que sigue inyectando el `MessageService` pero como lo llamará con tanta frecuencia, envuélvalo en un método privado `log()`:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
Define the `heroesUrl` of the form `:base/:collectionName` with the address of the heroes resource on the server.
|
Defina el `heroesUrl` del formulario `:base/:collectionName` con la dirección del recurso heroes en el servidor.
|
||||||
Here `base` is the resource to which requests are made,
|
Aquí `base` es el recurso al que se hacen las solicitudes,
|
||||||
and `collectionName` is the heroes data object in the `in-memory-data-service.ts`.
|
y `collectionName` es el objeto de datos de héroes en `in-memory-data-service.ts`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
### Get heroes with `HttpClient`
|
### Consigue héroes con `HttpClient`
|
||||||
|
|
||||||
The current `HeroService.getHeroes()`
|
El actual `HeroService.getHeroes()`
|
||||||
uses the RxJS `of()` function to return an array of mock heroes
|
usa la función RxJS `of()` para devolver una serie de héroes simulados
|
||||||
as an `Observable<Hero[]>`.
|
como un `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>
|
</code-example>
|
||||||
|
|
||||||
Convert that method to use `HttpClient` as follows:
|
Convierta ese método para usar `HttpClient` de la siguiente manera:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1">
|
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Refresh the browser. The hero data should successfully load from the
|
Actualiza el navegador. Los datos del héroe deben cargarse correctamente desde el
|
||||||
mock server.
|
servidor simulado.
|
||||||
|
|
||||||
You've swapped `of()` for `http.get()` and the app keeps working without any other changes
|
Ha cambiado `of()` por `http.get()` y la aplicación sigue funcionando sin ningún otro cambio
|
||||||
because both functions return an `Observable<Hero[]>`.
|
porque ambas funciones devuelven un `Observable <Hero[]>`.
|
||||||
|
|
||||||
### `HttpClient` methods return one value
|
### Los métodos `HttpClient` devuelven un valor
|
||||||
|
|
||||||
All `HttpClient` methods return an RxJS `Observable` of something.
|
Todos los métodos `HttpClient` devuelven un RxJS `Observable` de algo.
|
||||||
|
|
||||||
HTTP is a request/response protocol.
|
HTTP es un protocolo de solicitud/respuesta.
|
||||||
You make a request, it returns a single response.
|
Realiza una solicitud, devuelve una sola respuesta.
|
||||||
|
|
||||||
In general, an observable _can_ return multiple values over time.
|
En general, un _can_ observable puede devolver múltiples valores a lo largo del tiempo.
|
||||||
An observable from `HttpClient` always emits a single value and then completes, never to emit again.
|
Un observable de `HttpClient` siempre emite un único valor y luego se completa, para nunca volver a emitir.
|
||||||
|
|
||||||
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.
|
Esta llamada particular a `HttpClient.get()` devuelve un `Observable <Hero[]>`; es decir, "un observable de un arreglo de héroes". En la práctica, solo devolverá un único conjunto de héroes.
|
||||||
|
|
||||||
### `HttpClient.get()` returns response data
|
### `HttpClient.get()` devuelve datos de respuesta
|
||||||
|
|
||||||
`HttpClient.get()` returns the body of the response as an untyped JSON object by default.
|
`HttpClient.get()` devuelve el cuerpo de la respuesta como un objeto JSON sin tipo de forma predeterminada.
|
||||||
Applying the optional type specifier, `<Hero[]>` , adds TypeScript capabilities, which reduce errors during compile time.
|
Al aplicar el especificador de tipo opcional, `<Hero[]>`, se agregan capacidades de TypeScript, que reducen los errores durante el tiempo de compilación.
|
||||||
|
|
||||||
The server's data API determines the shape of the JSON data.
|
La API de datos del servidor determina la forma de los datos JSON.
|
||||||
The _Tour of Heroes_ data API returns the hero data as an array.
|
La API de datos _Tour of Heroes_ devuelve los datos del héroe como una matriz.
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
Other APIs may bury the data that you want within an object.
|
Otras API pueden enterrar los datos que desea dentro de un objeto.
|
||||||
You might have to dig that data out by processing the `Observable` result
|
Puede que tenga que desenterrar esos datos procesando el resultado `Observable`
|
||||||
with the RxJS `map()` operator.
|
con el operador RxJS `map()`.
|
||||||
|
|
||||||
Although not discussed here, there's an example of `map()` in the `getHeroNo404()`
|
|
||||||
method included in the sample source code.
|
|
||||||
|
|
||||||
|
Aunque no se trata aquí, hay un ejemplo de `map()` en `getHeroNo404()`
|
||||||
|
método incluido en el código fuente de muestra.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Error handling
|
### Manejo de errores
|
||||||
|
|
||||||
Things go wrong, especially when you're getting data from a remote server.
|
Las cosas salen mal, especialmente cuando obtiene datos de un servidor remoto.
|
||||||
The `HeroService.getHeroes()` method should catch errors and do something appropriate.
|
El método `HeroService.getHeroes()` debería detectar errores y hacer algo apropiado.
|
||||||
|
|
||||||
To catch errors, you **"pipe" the observable** result from `http.get()` through an RxJS `catchError()` operator.
|
Para detectar errores, **"filtra" el resultado observable** desde `http.get()` a través de un operador RxJS `catchError()`.
|
||||||
|
|
||||||
Import the `catchError` symbol from `rxjs/operators`, along with some other operators you'll need later.
|
Importe el símbolo `catchError` desde `rxjs/operadores`, junto con algunos otros operadores que necesitará más adelante.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
Now extend the observable result with the `pipe()` method and
|
Ahora extienda el resultado observable con el método `pipe()` y
|
||||||
give it a `catchError()` operator.
|
darle un operador `catchError()`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHeroes-2" header="src/app/hero.service.ts">
|
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHeroes-2" header="src/app/hero.service.ts">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The `catchError()` operator intercepts an **`Observable` that failed**.
|
El operador `catchError()` intercepta un **`Observable` que falló**.
|
||||||
It passes the error an error handler that can do what it wants with the error.
|
Pasa el error a un controlador de errores que puede hacer lo que quiera con el error.
|
||||||
|
|
||||||
The following `handleError()` method reports the error and then returns an
|
El siguiente método `handleError()` informa el error y luego devuelve un
|
||||||
innocuous result so that the application keeps working.
|
resultado inocuo para que la aplicación siga funcionando.
|
||||||
|
|
||||||
#### `handleError`
|
#### `handleError`
|
||||||
|
|
||||||
The following `handleError()` will be shared by many `HeroService` methods
|
El siguiente `handleError()` será compartido por muchos métodos `HeroService`
|
||||||
so it's generalized to meet their different needs.
|
así que está generalizado para satisfacer sus diferentes necesidades.
|
||||||
|
|
||||||
Instead of handling the error directly, it returns an error handler function to `catchError` that it
|
En lugar de manejar el error directamente, devuelve una función de controlador de errores a `catchError` que
|
||||||
has configured with both the name of the operation that failed and a safe return value.
|
se configuró con el nombre de la operación que falló y un valor de retorno seguro.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
After reporting the error to the console, the handler constructs
|
Después de informar el error a la consola, el controlador construye
|
||||||
a user friendly message and returns a safe value to the app so the app can keep working.
|
un mensaje fácil de usar y devuelve un valor seguro a la aplicación para que la aplicación pueda seguir funcionando.
|
||||||
|
|
||||||
Because each service method returns a different kind of `Observable` result,
|
Como cada método de servicio devuelve un tipo diferente de resultado 'Observable',
|
||||||
`handleError()` takes a type parameter so it can return the safe value as the type that the app expects.
|
`handleError ()` toma un parámetro de tipo para que pueda devolver el valor seguro como el tipo que la aplicación espera.
|
||||||
|
|
||||||
### Tap into the Observable
|
### Tap en el Observable
|
||||||
|
|
||||||
The `HeroService` methods will **tap** into the flow of observable values
|
Los métodos `HeroService` **aprovecharán** el flujo de valores observables
|
||||||
and send a message, via the `log()` method, to the message area at the bottom of the page.
|
y envíe un mensaje, a través del método `log()`, al área de mensajes en la parte inferior de la página.
|
||||||
|
|
||||||
They'll do that with the RxJS `tap()` operator,
|
Lo harán con el operador RxJS `tap()`,
|
||||||
which looks at the observable values, does something with those values,
|
que mira los valores observables, hace algo con esos valores,
|
||||||
and passes them along.
|
y los pasa
|
||||||
The `tap()` call back doesn't touch the values themselves.
|
La devolución de llamada `tap()` no toca los valores en sí mismos.
|
||||||
|
|
||||||
Here is the final version of `getHeroes()` with the `tap()` that logs the operation.
|
Aquí está la versión final de `getHeroes()` con el `tap()` que registra la operación.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="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>
|
</code-example>
|
||||||
|
|
||||||
### Get hero by id
|
### Obtener héroe por id
|
||||||
|
|
||||||
Most web APIs support a _get by id_ request in the form `:baseURL/:id`.
|
La mayoría de las API web admiten una solicitud _get by id_ en la forma `: 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
|
Aquí, la _base URL_ es el `heroesURL` definido en la [Heroes y HTTP](tutorial/toh-pt6#heroes-and-http) sección (`api/heroes`) y _id_ es
|
||||||
the number of the hero that you want to retrieve. For example, `api/heroes/11`.
|
El número del héroe que quieres recuperar. Por ejemplo, `api/heroes/11`.
|
||||||
|
|
||||||
Update the `HeroService` `getHero()` method with the following to make that request:
|
Actualice el método `HeroService`` getHero()` con lo siguiente para hacer esa solicitud:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts"></code-example>
|
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts"></code-example>
|
||||||
|
|
||||||
There are three significant differences from `getHeroes()`:
|
Hay tres diferencias significativas de `getHeroes()`:
|
||||||
|
|
||||||
* `getHero()` constructs a request URL with the desired hero's id.
|
* `getHero()` construye una URL de solicitud con la identificación del héroe deseado.
|
||||||
* The server should respond with a single hero rather than an array of heroes.
|
* El servidor debe responder con un solo héroe en lugar de una serie de héroes.
|
||||||
* `getHero()` returns an `Observable<Hero>` ("_an observable of Hero objects_")
|
* `getHero()` devuelve un `Observable <Hero>` ("_un observable de objetos Hero_")
|
||||||
rather than an observable of hero _arrays_ .
|
en lugar de un observable de _arreglos_ de héroes.
|
||||||
|
|
||||||
## Update heroes
|
## Actualizar héroes
|
||||||
|
|
||||||
Edit a hero's name in the hero detail view.
|
Edite el nombre de un héroe en la vista de detalles del héroe.
|
||||||
As you type, the hero name updates the heading at the top of the page.
|
A medida que escribe, el nombre del héroe actualiza el encabezado en la parte superior de la página.
|
||||||
But when you click the "go back button", the changes are lost.
|
Pero cuando hace clic en el "botón volver", los cambios se pierden.
|
||||||
|
|
||||||
If you want changes to persist, you must write them back to
|
Si desea que los cambios persistan, debe volver a escribirlos en
|
||||||
the server.
|
el servidor.
|
||||||
|
|
||||||
At the end of the hero detail template, add a save button with a `click` event
|
Al final de la plantilla de detalles del héroe, agregue un botón de guardar con un evento de "clic"
|
||||||
binding that invokes a new component method named `save()`.
|
enlace que invoca un nuevo método de componente llamado `save()`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.html" region="save" header="src/app/hero-detail/hero-detail.component.html (save)"></code-example>
|
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.html" region="save" header="src/app/hero-detail/hero-detail.component.html (save)"></code-example>
|
||||||
|
|
||||||
In the `HeroDetail` component class, add the following `save()` method, which persists hero name changes using the hero service
|
En la clase de componente `HeroDetail`, agregue el siguiente método `save()`, que persiste los cambios de nombre de héroe usando el servicio de héroe
|
||||||
`updateHero()` method and then navigates back to the previous view.
|
`updateHero()` y luego navega de regreso a la vista anterior.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.ts" region="save" header="src/app/hero-detail/hero-detail.component.ts (save)"></code-example>
|
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.ts" region="save" header="src/app/hero-detail/hero-detail.component.ts (save)"></code-example>
|
||||||
|
|
||||||
#### Add `HeroService.updateHero()`
|
#### Agregar `HeroService.updateHero ()`
|
||||||
|
|
||||||
The overall structure of the `updateHero()` method is similar to that of
|
La estructura general del método `updateHero()` es similar a la de
|
||||||
`getHeroes()`, but it uses `http.put()` to persist the changed hero
|
`getHeroes()`, pero usa `http.put()` para persistir el héroe cambiado
|
||||||
on the server. Add the following to the `HeroService`.
|
en el servidor Agregue lo siguiente al `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>
|
</code-example>
|
||||||
|
|
||||||
The `HttpClient.put()` method takes three parameters:
|
El método `HttpClient.put()` toma tres parámetros:
|
||||||
* the URL
|
* la URL
|
||||||
* the data to update (the modified hero in this case)
|
* los datos para actualizar (el héroe modificado en este caso)
|
||||||
* options
|
* opciones
|
||||||
|
|
||||||
The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id`.
|
La URL no se modifica. La API web de héroes sabe qué héroe actualizar al mirar el "id" del héroe.
|
||||||
|
|
||||||
The heroes web API expects a special header in HTTP save requests.
|
La API web de héroes espera un encabezado especial en las solicitudes de guardado HTTP.
|
||||||
That header is in the `httpOptions` constant defined in the `HeroService`. Add the following to the `HeroService` class.
|
Ese encabezado está en la constante `httpOptions` definida en el `HeroService`. Agregue lo siguiente a la clase `HeroService`.
|
||||||
|
|
||||||
<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>
|
</code-example>
|
||||||
|
|
||||||
Refresh the browser, change a hero name and save your change. The `save()`
|
Actualiza el navegador, cambia el nombre de un héroe y guarda tu cambio. El `save()`
|
||||||
method in `HeroDetailComponent` navigates to the previous view.
|
El método en `HeroDetailComponent` navega a la vista anterior.
|
||||||
The hero now appears in the list with the changed name.
|
El héroe ahora aparece en la lista con el nombre cambiado.
|
||||||
|
|
||||||
|
## Agrega un nuevo héroe
|
||||||
|
|
||||||
## Add a new hero
|
Para agregar un héroe, esta aplicación solo necesita el nombre del héroe. Puede utilizar un `<input>`
|
||||||
|
elemento emparejado con un botón Agregar.
|
||||||
|
|
||||||
To add a hero, this app only needs the hero's name. You can use an `<input>`
|
Inserte lo siguiente en la plantilla `HeroesComponent`, justo después
|
||||||
element paired with an add button.
|
El encabezado:
|
||||||
|
|
||||||
Insert the following into the `HeroesComponent` template, just after
|
|
||||||
the heading:
|
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="add" header="src/app/heroes/heroes.component.html (add)"></code-example>
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="add" header="src/app/heroes/heroes.component.html (add)"></code-example>
|
||||||
|
|
||||||
In response to a click event, call the component's click handler, `add()`, and then
|
En respuesta a un evento de clic, llame al controlador de clic del componente, `add()`, y luego
|
||||||
clear the input field so that it's ready for another name. Add the following to the
|
borre el campo de entrada para que esté listo para otro nombre. Agregue lo siguiente al
|
||||||
`HeroesComponent` class:
|
Clase `Componente de héroes`:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="add" header="src/app/heroes/heroes.component.ts (add)"></code-example>
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="add" header="src/app/heroes/heroes.component.ts (add)"></code-example>
|
||||||
|
|
||||||
When the given name is non-blank, the handler creates a `Hero`-like object
|
Cuando el nombre de pila no está en blanco, el controlador crea un objeto similar a un "Héroe"
|
||||||
from the name (it's only missing the `id`) and passes it to the services `addHero()` method.
|
del nombre (sólo falta el `id`) y lo pasa al método de servicios `addHero()`.
|
||||||
|
|
||||||
When `addHero()` saves successfully, the `subscribe()` callback
|
Cuando `addHero()` se guarda correctamente, la devolución de llamada `subscribe()`
|
||||||
receives the new hero and pushes it into to the `heroes` list for display.
|
recibe el nuevo héroe y lo empuja a la lista de "héroes" para mostrarlo.
|
||||||
|
|
||||||
Add the following `addHero()` method to the `HeroService` class.
|
Agregue el siguiente método `addHero()` a la clase `HeroService`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="addHero" header="src/app/hero.service.ts (addHero)"></code-example>
|
<code-example path="toh-pt6/src/app/hero.service.ts" region="addHero" header="src/app/hero.service.ts (addHero)"></code-example>
|
||||||
|
|
||||||
`addHero()` differs from `updateHero()` in two ways:
|
`addHero()` difiere de `updateHero()` en dos formas:
|
||||||
|
|
||||||
* It calls `HttpClient.post()` instead of `put()`.
|
* Llama a `HttpClient.post()` en lugar de a `put()`.
|
||||||
* It expects the server to generate an id for the new hero,
|
* Espera que el servidor genere una identificación para el nuevo héroe,
|
||||||
which it returns in the `Observable<Hero>` to the caller.
|
que devuelve en el `Observable<Hero>` a la persona que llama.
|
||||||
|
|
||||||
Refresh the browser and add some heroes.
|
Actualiza el navegador y agrega algunos héroes.
|
||||||
|
|
||||||
## Delete a hero
|
## Eliminar un héroe
|
||||||
|
|
||||||
Each hero in the heroes list should have a delete button.
|
Cada héroe de la lista de héroes debe tener un botón de eliminación.
|
||||||
|
|
||||||
Add the following button element to the `HeroesComponent` template, after the hero
|
Agregue el siguiente elemento de botón a la plantilla `HeroesComponent`, después del héroe
|
||||||
name in the repeated `<li>` element.
|
nombre en el elemento repetido `<li>`.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="delete"></code-example>
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="delete"></code-example>
|
||||||
|
|
||||||
The HTML for the list of heroes should look like this:
|
El HTML de la lista de héroes debería verse así:
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list of heroes)"></code-example>
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list of heroes)"></code-example>
|
||||||
|
|
||||||
To position the delete button at the far right of the hero entry,
|
Para colocar el botón de eliminar en el extremo derecho de la entrada del héroe,
|
||||||
add some CSS to the `heroes.component.css`. You'll find that CSS
|
agregue algo de CSS al `heroes.component.css`. Encontrarás ese CSS
|
||||||
in the [final review code](#heroescomponent) below.
|
en el [código de revisión final](#heroescomponent) a continuación.
|
||||||
|
|
||||||
Add the `delete()` handler to the component class.
|
Agregue el controlador `delete()` a la clase del componente.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example>
|
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example>
|
||||||
|
|
||||||
Although the component delegates hero deletion to the `HeroService`,
|
Aunque el componente delega la eliminación de héroes al `HeroService`,
|
||||||
it remains responsible for updating its own list of heroes.
|
sigue siendo responsable de actualizar su propia lista de héroes.
|
||||||
The component's `delete()` method immediately removes the _hero-to-delete_ from that list,
|
El método `delete()` del componente elimina inmediatamente el _hero-to-delete_ de esa lista,
|
||||||
anticipating that the `HeroService` will succeed on the server.
|
anticipando que el `HeroService` tendrá éxito en el servidor.
|
||||||
|
|
||||||
There's really nothing for the component to do with the `Observable` returned by
|
Realmente no hay nada que ver el componente con el "Observable" devuelto por
|
||||||
`heroService.delete()` **but it must subscribe anyway**.
|
`heroService.delete()` **pero debe suscribirse de todos modos**.
|
||||||
|
|
||||||
<div class="alert is-important">
|
<div class="alert is-important">
|
||||||
|
|
||||||
If you neglect to `subscribe()`, the service will not send the delete request to the server.
|
Si se olvida de `subscribe()`, el servicio no enviará la solicitud de eliminación al servidor.
|
||||||
As a rule, an `Observable` _does nothing_ until something subscribes.
|
Como regla general, un "Observable" _no hace nada_ hasta que algo se suscribe.
|
||||||
|
|
||||||
Confirm this for yourself by temporarily removing the `subscribe()`,
|
Confirme esto por sí mismo eliminando temporalmente el `subscribe()`,
|
||||||
clicking "Dashboard", then clicking "Heroes".
|
haciendo clic en "Panel de control", luego en "Héroes".
|
||||||
You'll see the full list of heroes again.
|
Verás la lista completa de héroes nuevamente.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Next, add a `deleteHero()` method to `HeroService` like this.
|
A continuación, agregue un método `deleteHero()` a `HeroService` como este.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="deleteHero" header="src/app/hero.service.ts (delete)"></code-example>
|
<code-example path="toh-pt6/src/app/hero.service.ts" region="deleteHero" header="src/app/hero.service.ts (delete)"></code-example>
|
||||||
|
|
||||||
Note the following key points:
|
Tenga en cuenta los siguientes puntos clave:
|
||||||
|
|
||||||
* `deleteHero()` calls `HttpClient.delete()`.
|
* `deleteHero()` llama a `HttpClient.delete()`.
|
||||||
* The URL is the heroes resource URL plus the `id` of the hero to delete.
|
* La URL es la URL del recurso de héroes más el "id" del héroe a eliminar.
|
||||||
* You don't send data as you did with `put()` and `post()`.
|
* No envías datos como lo hiciste con `put()` y `post()`.
|
||||||
* You still send the `httpOptions`.
|
* Aún envías las `httpOptions`.
|
||||||
|
|
||||||
Refresh the browser and try the new delete functionality.
|
Actualice el navegador y pruebe la nueva función de eliminación.
|
||||||
|
|
||||||
## Search by name
|
## Buscar por nombre
|
||||||
|
|
||||||
In this last exercise, you learn to chain `Observable` operators together
|
En este último ejercicio, aprenderá a encadenar operadores "observables"
|
||||||
so you can minimize the number of similar HTTP requests
|
para que pueda minimizar la cantidad de solicitudes HTTP similares
|
||||||
and consume network bandwidth economically.
|
y consumir ancho de banda de la red de forma económica.
|
||||||
|
|
||||||
You will add a heroes search feature to the Dashboard.
|
Agregará una función de búsqueda de héroes al Tablero.
|
||||||
As the user types a name into a search box,
|
A medida que el usuario escribe un nombre en un cuadro de búsqueda,
|
||||||
you'll make repeated HTTP requests for heroes filtered by that name.
|
harás solicitudes HTTP repetidas para héroes filtrados por ese nombre.
|
||||||
Your goal is to issue only as many requests as necessary.
|
Su objetivo es emitir solo tantas solicitudes como sea necesario.
|
||||||
|
|
||||||
#### `HeroService.searchHeroes()`
|
#### `HeroService.searchHeroes()`
|
||||||
|
|
||||||
Start by adding a `searchHeroes()` method to the `HeroService`.
|
Comience agregando un método `searchHeroes()` al `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>
|
</code-example>
|
||||||
|
|
||||||
The method returns immediately with an empty array if there is no search term.
|
El método regresa inmediatamente con una matriz vacía si no hay un término de búsqueda.
|
||||||
The rest of it closely resembles `getHeroes()`, the only significant difference being
|
El resto se parece mucho a "getHeroes()", siendo la única diferencia significativa
|
||||||
the URL, which includes a query string with the search term.
|
la URL, que incluye una cadena de consulta con el término de búsqueda.
|
||||||
|
|
||||||
### Add search to the Dashboard
|
### Agregar búsqueda al panel
|
||||||
|
|
||||||
Open the `DashboardComponent` template and
|
Abra la plantilla `DashboardComponent` y
|
||||||
add the hero search element, `<app-hero-search>`, to the bottom of the markup.
|
agregue el elemento de búsqueda de héroe, `<app-hero-search>`, al final del marcado.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html"></code-example>
|
<code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html"></code-example>
|
||||||
|
|
||||||
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
|
Esta plantilla se parece mucho al repetidor `*ngFor` en la plantilla `HeroesComponent`.
|
||||||
|
|
||||||
For this to work, the next step is to add a component with a selector that matches `<app-hero-search>`.
|
Para que esto funcione, el siguiente paso es agregar un componente con un selector que coincida con `<app-hero-search>`.
|
||||||
|
|
||||||
|
|
||||||
### Create `HeroSearchComponent`
|
### Crear `HeroSearchComponent`
|
||||||
|
|
||||||
Create a `HeroSearchComponent` with the CLI.
|
Cree un "HeroSearchComponent" con El Cli.
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
<code-example language="sh" class="code-shell">
|
||||||
ng generate component hero-search
|
ng generate component hero-search
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations.
|
El Cli genera los tres archivos de `HeroSearchComponent` y agrega el componente a las declaraciones en `AppModule`.
|
||||||
|
|
||||||
Replace the generated `HeroSearchComponent` template with an `<input>` and a list of matching search results, as follows.
|
Reemplace la plantilla `HeroSearchComponent` generada con un `<input>` y una lista de resultados de búsqueda coincidentes, de la siguiente manera.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html"></code-example>
|
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html"></code-example>
|
||||||
|
|
||||||
Add private CSS styles to `hero-search.component.css`
|
Agregue estilos CSS privados a `hero-search.component.css`
|
||||||
as listed in the [final code review](#herosearchcomponent) below.
|
como se indica en la [revisión final del código](#herosearchcomponent) a continuación.
|
||||||
|
|
||||||
As the user types in the search box, an input event binding calls the
|
A medida que el usuario escribe en el cuadro de búsqueda, un enlace de evento de entrada llama al
|
||||||
component's `search()` method with the new search box value.
|
el método `search()` del componente con el nuevo valor del cuadro de búsqueda.
|
||||||
|
|
||||||
{@a asyncpipe}
|
{@a asyncpipe}
|
||||||
|
|
||||||
### `AsyncPipe`
|
### `AsyncPipe`
|
||||||
|
|
||||||
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.
|
El `*ngFor` repite los objetos hero. Note que el `*ngFor` itera sobre una lista llamada `heroes$`, no sobre `heroes`. El `$` es una convención que indica que `heroes$` es un `Observable`, no un arreglo.
|
||||||
|
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
Since `*ngFor` can't do anything with an `Observable`, use the
|
Como `*ngFor` no puede hacer nada con un `Observable`, use el
|
||||||
pipe character (`|`) followed by `async`. This identifies Angular's `AsyncPipe` and subscribes to an `Observable` automatically so you won't have to
|
carácter de filtración (`|`) seguido de `async`. Esto identifica el "AsyncPipe" de Angular y se suscribe automáticamente a un "Observable" para que no tenga que
|
||||||
do so in the component class.
|
hacerlo en la clase de componente.
|
||||||
|
|
||||||
### Edit the `HeroSearchComponent` class
|
### Editar la clase `HeroSearchComponent`
|
||||||
|
|
||||||
Replace the generated `HeroSearchComponent` class and metadata as follows.
|
Reemplace la clase generada `HeroSearchComponent` y los metadatos de la siguiente manera.
|
||||||
|
|
||||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts"></code-example>
|
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts"></code-example>
|
||||||
|
|
||||||
Notice the declaration of `heroes$` as an `Observable`:
|
Observe la declaración de `heroes$` como un `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 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>
|
</code-example>
|
||||||
|
|
||||||
You'll set it in [`ngOnInit()`](#search-pipe).
|
Lo configurará en [`ngOnInit()`](#search-pipe).
|
||||||
Before you do, focus on the definition of `searchTerms`.
|
Antes de hacerlo, concéntrese en la definición de `searchTerms`.
|
||||||
|
|
||||||
### The `searchTerms` RxJS subject
|
### El sujeto RxJS `searchTerms`
|
||||||
|
|
||||||
The `searchTerms` property is an RxJS `Subject`.
|
La propiedad `searchTerms` es un `Sujeto` de RxJS.
|
||||||
|
|
||||||
<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>
|
<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.
|
Un `Sujeto` es tanto una fuente de valores observables como un `Observable` en sí mismo.
|
||||||
You can subscribe to a `Subject` as you would any `Observable`.
|
Puede suscribirse a un `Sujeto` como lo haría con cualquier `Observable`.
|
||||||
|
|
||||||
You can also push values into that `Observable` by calling its `next(value)` method
|
También puede insertar valores en ese `Observable` llamando a su método `next(value)`
|
||||||
as the `search()` method does.
|
como lo hace el método `search()`.
|
||||||
|
|
||||||
The event binding to the textbox's `input` event calls the `search()` method.
|
El evento vinculado al evento `input` del cuadro de texto llama al método `search()`.
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
@ -470,61 +466,61 @@ The `searchTerms` becomes an `Observable` emitting a steady stream of search ter
|
|||||||
|
|
||||||
{@a search-pipe}
|
{@a search-pipe}
|
||||||
|
|
||||||
### Chaining RxJS operators
|
### Encadenamiento de operadores RxJS
|
||||||
|
|
||||||
Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests,
|
Pasar un nuevo término de búsqueda directamente a `searchHeroes()` después de cada pulsación de tecla del usuario crearía una cantidad excesiva de solicitudes HTTP,
|
||||||
taxing server resources and burning through data plans.
|
gravando los recursos del servidor y quemando a través de planes de datos.
|
||||||
|
|
||||||
Instead, the `ngOnInit()` method pipes the `searchTerms` observable through a sequence of RxJS operators that reduce the number of calls to the `searchHeroes()`,
|
En cambio, el método `ngOnInit()` filtra los `searchTerms` observables a través de una secuencia de operadores RxJS que reducen el número de llamadas `searchHeroes()`,
|
||||||
ultimately returning an observable of timely hero search results (each a `Hero[]`).
|
en última instancia, devuelve un observable de resultados de búsqueda de héroes oportunos (cada uno un `Héroe[]`).
|
||||||
|
|
||||||
Here's a closer look at the code.
|
Aquí hay un vistazo más de cerca al código.
|
||||||
|
|
||||||
<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 path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="search">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Each operator works as follows:
|
Cada operador funciona de la siguiente manera:
|
||||||
|
|
||||||
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
* `debounceTime (300)` espera hasta que el flujo de nuevos eventos de cadena se detenga durante 300 milisegundos
|
||||||
before passing along the latest string. You'll never make requests more frequently than 300ms.
|
antes de pasar por la última cuerda. Nunca hará solicitudes con más frecuencia que 300 ms.
|
||||||
|
|
||||||
* `distinctUntilChanged()` ensures that a request is sent only if the filter text changed.
|
* `distinctUntilChanged ()` asegura que una solicitud se envíe solo si el texto del filtro cambió.
|
||||||
|
|
||||||
* `switchMap()` calls the search service for each search term that makes it through `debounce()` and `distinctUntilChanged()`.
|
* `switchMap ()` llama al servicio de búsqueda para cada término de búsqueda que pasa por `debounce ()` y 'distinctUntilChanged () `.
|
||||||
It cancels and discards previous search observables, returning only the latest search service observable.
|
Cancela y descarta los observables de búsqueda anteriores, devolviendo solo el último servicio de búsqueda observable.
|
||||||
|
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html),
|
Con el [operador de switchMap](http://www.learnrxjs.io/operators/transformation/switchmap.html),
|
||||||
every qualifying key event can trigger an `HttpClient.get()` method call.
|
cada evento clave que califique puede activar una llamada al método `HttpClient.get ()`.
|
||||||
Even with a 300ms pause between requests, you could have multiple HTTP requests in flight
|
Incluso con una pausa de 300 ms entre solicitudes, podría tener varias solicitudes HTTP en vuelo
|
||||||
and they may not return in the order sent.
|
y no pueden regresar en el orden enviado.
|
||||||
|
|
||||||
`switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call.
|
`switchMap ()` conserva el orden de solicitud original mientras devuelve solo lo observable de la llamada al método HTTP más reciente.
|
||||||
Results from prior calls are canceled and discarded.
|
Los resultados de llamadas anteriores se cancelan y descartan.
|
||||||
|
|
||||||
Note that canceling a previous `searchHeroes()` Observable
|
Tenga en cuenta que cancelar un `searchHeroes ()` anterior observable
|
||||||
doesn't actually abort a pending HTTP request.
|
en realidad no aborta una solicitud HTTP pendiente.
|
||||||
Unwanted results are simply discarded before they reach your application code.
|
Los resultados no deseados simplemente se descartan antes de que lleguen al código de su aplicación.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Remember that the component _class_ does not subscribe to the `heroes$` _observable_.
|
Recuerde que el componente _class_ no se suscribe a los `heroes$` _observable_.
|
||||||
That's the job of the [`AsyncPipe`](#asyncpipe) in the template.
|
Ese es el trabajo de [`Filtro asíncrono (asynpipe)`](#asyncpipe) en la plantilla.
|
||||||
|
|
||||||
#### Try it
|
#### Intentalo
|
||||||
|
|
||||||
Run the app again. In the *Dashboard*, enter some text in the search box.
|
Ejecute la aplicación nuevamente. En el *Tablero*, ingrese texto en el cuadro de búsqueda.
|
||||||
If you enter characters that match any existing hero names, you'll see something like this.
|
Si ingresas personajes que coinciden con cualquier nombre de héroe existente, verás algo como esto.
|
||||||
|
|
||||||
<div class="lightbox">
|
<div class="lightbox">
|
||||||
<img src='generated/images/guide/toh/toh-hero-search.png' alt="Hero Search Component">
|
<img src='generated/images/guide/toh/toh-hero-search.png' alt="Hero Search Component">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Final code review
|
## Revisión final del código
|
||||||
|
|
||||||
Here are the code files discussed on this page (all in the `src/app/` folder).
|
Aquí están los archivos de código discutidos en esta página (todos en la carpeta `src/app/`).
|
||||||
|
|
||||||
{@a heroservice}
|
{@a heroservice}
|
||||||
{@a inmemorydataservice}
|
{@a inmemorydataservice}
|
||||||
@ -547,7 +543,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
{@a heroescomponent}
|
{@a heroescomponent}
|
||||||
#### `HeroesComponent`
|
#### `Componente de heroes`
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -565,7 +561,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
{@a herodetailcomponent}
|
{@a herodetailcomponent}
|
||||||
#### `HeroDetailComponent`
|
#### `Componete de detalles de el heroe`
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -579,7 +575,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
{@a dashboardcomponent}
|
{@a dashboardcomponent}
|
||||||
#### `DashboardComponent`
|
#### `Componente de panel(dashboard)`
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -589,7 +585,7 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
{@a herosearchcomponent}
|
{@a herosearchcomponent}
|
||||||
#### `HeroSearchComponent`
|
#### `Componente de búsqueda de héroe`
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane
|
<code-pane
|
||||||
@ -606,16 +602,17 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
|||||||
</code-pane>
|
</code-pane>
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
## Summary
|
## Resumen
|
||||||
|
|
||||||
You're at the end of your journey, and you've accomplished a lot.
|
Este es el final de su viaje y ha logrado mucho.
|
||||||
* You added the necessary dependencies to use HTTP in the app.
|
|
||||||
* You refactored `HeroService` to load heroes from a web API.
|
|
||||||
* You extended `HeroService` to support `post()`, `put()`, and `delete()` methods.
|
|
||||||
* You updated the components to allow adding, editing, and deleting of heroes.
|
|
||||||
* You configured an in-memory web API.
|
|
||||||
* You learned how to use observables.
|
|
||||||
|
|
||||||
This concludes the "Tour of Heroes" tutorial.
|
* Agrego las dependencias necesarias para usar HTTP en la aplicación.
|
||||||
You're ready to learn more about Angular development in the fundamentals section,
|
* Refactorizó `HeroService` para cargar héroes desde una API web.
|
||||||
starting with the [Architecture](guide/architecture "Architecture") guide.
|
* Extendió `HeroService` para admitir los métodos `post()`, `put()` y `delete()`.
|
||||||
|
* Actualizo los componentes para permitir agregar, editar y eliminar héroes.
|
||||||
|
* Configuro una API web en memoria.
|
||||||
|
* Aprendio a usar observables.
|
||||||
|
|
||||||
|
Esto concluye el tutorial "Tour de los Heroes".
|
||||||
|
Estás listo para aprender más sobre el desarrollo Angular en la sección de fundamentos,
|
||||||
|
comenzando con la guía [Arquitectura](guide/architecture "Architecture") guide.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user