623 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			623 lines
		
	
	
		
			23 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
 | |
|   # Getting and Saving Data
 | |
| 
 | |
|   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="bash").
 | |
|     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('app/app.module.ts', 'v1','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
 | |
|     We're importing the  `InMemoryWebApiModule` and adding it to the module `imports`.
 | |
|     The  `InMemoryWebApiModule` replaces the default `Http` client backend —
 | |
|     the supporting service that talks to the remote server —
 | |
|     with an  _in-memory web API alternative service_.
 | |
| 
 | |
|   +makeExcerpt(_appModuleTsVsMainTs, 'in-mem-web-api', '')
 | |
| 
 | |
|   :marked
 | |
|     The `forRoot` configuration method takes an `InMemoryDataService` class
 | |
|     that primes the in-memory database as follows:
 | |
| 
 | |
| +makeExample('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/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('app/hero.service.ts (updated getHeroes and new class members)', 'getHeroes')
 | |
| 
 | |
| :marked
 | |
|   Our updated import statements are now:
 | |
| 
 | |
| +makeExcerpt('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('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('app/hero.service.ts', 'rxjs', '')
 | |
| 
 | |
|   :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('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('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('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.
 | |
| 
 | |
|   ### Unchanged `getHeroes` API
 | |
| 
 | |
|   Although we made significant *internal* changes to `getHeroes()`, the public signature did not change.
 | |
|   We still return a !{_Promise}. We won't have to update any of the components that call `getHeroes()`.
 | |
| 
 | |
|   Our stakeholders are thrilled with the added flexibility from the API integration.
 | |
|   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!
 | |
| 
 | |
| .l-sub-section
 | |
|   :marked
 | |
|     Updates weren't lost before, what's happening?
 | |
|     When the app used a list of mock heroes, changes were made directly to the
 | |
|     hero objects in 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.
 | |
| 
 | |
| :marked
 | |
|   ### 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('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('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('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('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('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('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('app/heroes.component.html', 'delete', '')
 | |
| 
 | |
| :marked
 | |
|   The `<li>` element should now look like this:
 | |
| 
 | |
| +makeExcerpt('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('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('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('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 [RxJS Observables](http://reactivex.io/rxjs/) library.
 | |
|     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('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.
 | |
|   <span if-docs="ts">Another notable difference: we no longer call `toPromise`,
 | |
|   we simply return the *observable* instead.</span>
 | |
| 
 | |
|   ### 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('app/hero-search.component.html')
 | |
| :marked
 | |
|   We'll also want to add styles for the new component.
 | |
| +makeExample('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('app/hero-search.component.ts')
 | |
| 
 | |
| :marked
 | |
|   #### Search terms
 | |
| 
 | |
|   Let's focus on the `!{_priv}searchTerms`:
 | |
| 
 | |
| +makeExcerpt('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('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.
 | |
| 
 | |
|     ### Import RxJS operators
 | |
|     The RxJS operators are not available in Angular's base `Observable` implementation.
 | |
|     We have to extend  `Observable` by *importing* them.
 | |
| 
 | |
|     We could extend `Observable` with just the operators we need here by
 | |
|     including the pertinent `import` statements at the top of this file.
 | |
| 
 | |
|   .l-sub-section
 | |
|     :marked
 | |
|       Many authorities say we should do just that.
 | |
|   :marked
 | |
|     We take a different approach in this example.
 | |
|     We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
 | |
| 
 | |
|   +makeExample('app/rxjs-extensions.ts')(format='.')
 | |
| 
 | |
|   :marked
 | |
|     We load them all at once by importing `rxjs-extensions` at the top of `AppModule`.
 | |
| 
 | |
|   +makeExcerpt('app/app.module.ts', 'rxjs-extensions')(format='.')
 | |
| 
 | |
| :marked
 | |
|   ### Add the search component to the dashboard
 | |
| 
 | |
|   We add the hero search HTML element to the bottom of the `DashboardComponent` template.
 | |
| 
 | |
| +makeExample('app/dashboard.component.html')(format='.')
 | |
| 
 | |
| - var _declarations = _docsFor == 'dart' ? 'directives' : 'declarations'
 | |
| - var declFile = _docsFor == 'dart' ? 'app/dashboard.component.ts' : '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 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 rxjs-extensions.ts
 | |
|         .file hero.service.ts
 | |
|         .file heroes.component.css
 | |
|         .file heroes.component.html
 | |
|         .file heroes.component.ts
 | |
|         .file main.ts
 | |
|         .file in-memory-data.service.ts (new)
 | |
|       .file node_modules ...
 | |
|       .file index.html
 | |
|       .file package.json
 | |
|       .file styles.css
 | |
|       .file systemjs.config.js
 | |
|       .file tsconfig.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/app/app.component.ts,
 | |
|      toh-6/ts/app/app.module.ts,
 | |
|      toh-6/ts/app/heroes.component.ts,
 | |
|      toh-6/ts/app/heroes.component.html,
 | |
|      toh-6/ts/app/heroes.component.css,
 | |
|      toh-6/ts/app/hero-detail.component.ts,
 | |
|      toh-6/ts/app/hero-detail.component.html,
 | |
|      toh-6/ts/app/hero.service.ts,
 | |
|      toh-6/ts/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/app/hero-search.service.ts,
 | |
|     toh-6/ts/app/hero-search.component.ts,
 | |
|     toh-6/ts/app/hero-search.component.html,
 | |
|     toh-6/ts/app/hero-search.component.css,
 | |
|     toh-6/ts/app/rxjs-extensions.ts`,
 | |
|     null,
 | |
|     `hero-search.service.ts,
 | |
|     hero-search.component.ts,
 | |
|     hero-search.component.html,
 | |
|     hero-search.component.css,
 | |
|     rxjs-extensions.ts`
 | |
|   )
 |