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