From 56ccd5e6a183a048e692a5eeef47450d4ff6ef98 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 10 May 2017 10:06:04 +0100 Subject: [PATCH] docs(aio): remove excess h1s from guides Only one h1 is allowed per document. (Also took the opportunity to remove unnecessary blank lines from these docs, and a bit of general tidying.) Closes #16193 --- aio/content/guide/http.md | 632 +++++++------------------------- aio/content/tutorial/toh-pt6.md | 587 ++++++----------------------- 2 files changed, 239 insertions(+), 980 deletions(-) diff --git a/aio/content/guide/http.md b/aio/content/guide/http.md index cb13fa1d76..34b95516fb 100644 --- a/aio/content/guide/http.md +++ b/aio/content/guide/http.md @@ -1,26 +1,14 @@ -@title -HTTP - -@intro -Use HTTP to talk to a remote server. - -@description - - +# HTTP [HTTP](https://tools.ietf.org/html/rfc2616) is the primary protocol for browser/server communication.
- - -The [`WebSocket`](https://tools.ietf.org/html/rfc6455) protocol is another important communication technology; -it isn't covered in this page. + The [`WebSocket`](https://tools.ietf.org/html/rfc6455) protocol is another important communication technology; + it isn't covered in this page.
- - Modern browsers support two HTTP-based APIs: [XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and [JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support @@ -28,88 +16,26 @@ Modern browsers support two HTTP-based APIs: The Angular HTTP library simplifies application programming with the **XHR** and **JSONP** APIs. - - A live example illustrates these topics. - {@a demos} - - -# Demos +## Demos This page describes server communication with the help of the following demos: - - * [The Tour of Heroes *HTTP* client demo](guide/http#http-client). * [Fall back to Promises](guide/http#promises). * [Cross-Origin Requests: Wikipedia example](guide/http#cors). * [More fun with Observables](guide/http#more-observables). - The root `AppComponent` orchestrates these demos: - - - - + {@a http-providers} -# Providing HTTP services +## Providing HTTP services First, configure the application to use server communication facilities. @@ -119,54 +45,39 @@ The `Http` client is one of a family of services in the Angular HTTP library.
- - -When importing from the `@angular/http` module, SystemJS knows how to load services from -the Angular HTTP library -because the `systemjs.config.js` file maps to that module name. - + When importing from the `@angular/http` module, SystemJS knows how to load services from + the Angular HTTP library + because the `systemjs.config.js` file maps to that module name.
- Before you can use the `Http` client, you need to register it as a service provider with the dependency injection system.
- - -Read about providers in the [Dependency Injection](guide/dependency-injection) page. - + Read about providers in the [Dependency Injection](guide/dependency-injection) page.
- Register providers by importing other NgModules to the root NgModule in `app.module.ts`. - - - - - - - + Begin by importing the necessary members. The newcomers are the `HttpModule` and the `JsonpModule` from the Angular HTTP library. For more information about imports and related terminology, see the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) on the `import` statement. To add these modules to the application, pass them to the `imports` array in the root `@NgModule`. +
- - -The `HttpModule` is necessary for making HTTP calls. -Though the `JsonpModule` isn't necessary for plain HTTP, -there is a JSONP demo later in this page. -Loading its module now saves time. + The `HttpModule` is necessary for making HTTP calls. + Though the `JsonpModule` isn't necessary for plain HTTP, + there is a JSONP demo later in this page. + Loading its module now saves time.
@@ -185,15 +96,9 @@ It works like this: ToH mini app - - This demo has a single component, the `HeroListComponent`. Here's its template: - - - - - + It presents the list of heroes with an `ngFor`. Below the list is an input box and an *Add Hero* button where you can enter the names of new heroes @@ -205,21 +110,14 @@ the event binding clears it to make it ready for a new hero name. Below the button is an area for an error message. - {@a oninit} - - {@a HeroListComponent} - ### The *HeroListComponent* class + Here's the component class: - - - - - + Angular [injects](guide/dependency-injection) a `HeroService` into the constructor and the component calls that service to fetch and save data. @@ -235,19 +133,16 @@ you **don't** call the service's `get` method in the component's constructor. Instead, call it inside the `ngOnInit` [lifecycle hook](guide/lifecycle-hooks) and rely on Angular to call `ngOnInit` when it instantiates this component. +
- - -This is a *best practice*. -Components are easier to test and debug when their constructors are simple, and all real work -(especially calling a remote server) is handled in a separate method. + This is a *best practice*. + Components are easier to test and debug when their constructors are simple, and all real work + (especially calling a remote server) is handled in a separate method.
- - The service's `getHeroes()` and `create()` methods return an `Observable` of hero data that the Angular Http client fetched from the server. Think of an `Observable` as a stream of events published by some source. @@ -255,12 +150,9 @@ To listen for events in this stream, ***subscribe*** to the `Observable`. These subscriptions specify the actions to take when the web request produces a success event (with the hero data in the event payload) or a fail event (with the error in the payload). - With a basic understanding of the component, you're ready to look inside the `HeroService`. - {@a HeroService} - {@a fetch-data} ## Fetch data with _http.get()_ @@ -268,58 +160,35 @@ With a basic understanding of the component, you're ready to look inside the `He In many of the previous samples the app faked the interaction with the server by returning mock heroes in a service like this one: - - - - - + You can revise that `HeroService` to get the heroes from the server using the Angular Http client service: - - - - - + Notice that the Angular Http client service is [injected](guide/dependency-injection) into the `HeroService` constructor. - - - - - + Look closely at how to call `http.get`: - - - - - + You pass the resource URL to `get` and it calls the server which returns heroes.
+ The server returns heroes once you've set up the [in-memory web api](guide/http#in-mem-web-api) + described in the appendix below. + Alternatively, you can temporarily target a JSON file by changing the endpoint URL: - -The server returns heroes once you've set up the [in-memory web api](guide/http#in-mem-web-api) -described in the appendix below. -Alternatively, you can temporarily target a JSON file by changing the endpoint URL: - - - - - - +
- {@a rxjs} If you are familiar with asynchronous methods in modern JavaScript, you might expect the `get` method to return a promise. @@ -330,12 +199,10 @@ Clearly this is not a promise. In fact, the `http.get` method returns an **Observable** of HTTP Responses (`Observable`) from the RxJS library and `map()` is one of the RxJS *operators*. - {@a rxjs-library} - - ## RxJS library + RxJS is a third party library, endorsed by Angular, that implements the asynchronous Observable pattern. @@ -347,6 +214,7 @@ But you must take a critical extra step to make RxJS Observables usable: _you must import the RxJS operators individually_. ### Enable RxJS operators + The RxJS library is large. Size matters when building a production application and deploying it to mobile devices. You should include only necessary features. @@ -354,115 +222,79 @@ You should include only necessary features. Each code file should add the operators it needs by importing from an RxJS library. The `getHeroes()` method needs the `map()` and `catch()` operators so it imports them like this. - - - - - - + {@a extract-data} - ## Process the response object + Remember that the `getHeroes()` method used an `extractData()` helper method to map the `http.get` response object to heroes: - - - - - + The `response` object doesn't hold the data in a form the app can use directly. You must parse the response data into a JSON object. - {@a parse-to-json} - ### Parse to JSON - The response data are in JSON string form. The app must parse that string into JavaScript objects by calling `response.json()`. -
- - -This is not Angular's own design. -The Angular HTTP client follows the Fetch specification for the -[response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function. -That spec defines a `json()` method that parses the response body into a JavaScript object. - + This is not Angular's own design. + The Angular HTTP client follows the Fetch specification for the + [response object](https://fetch.spec.whatwg.org/#response-class) returned by the `Fetch` function. + That spec defines a `json()` method that parses the response body into a JavaScript object.
-
- - -Don't expect the decoded JSON to be the heroes array directly. -This server always wraps JSON results in an object with a `data` -property. You have to unwrap it to get the heroes. -This is conventional web API behavior, driven by -[security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside). - + Don't expect the decoded JSON to be the heroes array directly. + This server always wraps JSON results in an object with a `data` + property. You have to unwrap it to get the heroes. + This is conventional web API behavior, driven by + [security concerns](https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside).
-
- - -Make no assumptions about the server API. -Not all servers return an object with a `data` property. - + Make no assumptions about the server API. + Not all servers return an object with a `data` property.
- {@a no-return-response-object} - ### Do not return the response object + The `getHeroes()` method _could_ have returned the HTTP response but this wouldn't follow best practices. The point of a data service is to hide the server interaction details from consumers. The component that calls the `HeroService` only wants heroes and is kept separate from getting them, the code dealing with where they come from, and the response object. -
+
HTTP GET is delayed
- - -
- HTTP GET is delayed -
- - - -The `http.get` does **not send the request just yet.** This Observable is -[*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables), -which means that the request won't go out until something *subscribes* to the Observable. -That *something* is the [HeroListComponent](guide/http#subscribe). - + The `http.get` does **not send the request just yet.** This Observable is + [*cold*](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables), + which means that the request won't go out until something *subscribes* to the Observable. + That *something* is the [HeroListComponent](guide/http#subscribe).
- {@a error-handling} - ### Always handle errors An important part of dealing with I/O is anticipating errors by preparing to catch them @@ -472,13 +304,7 @@ but only if it says something that the user can understand and act upon. This simple app conveys that idea, albeit imperfectly, in the way it handles a `getHeroes` error. - - - - - - - + The `catch()` operator passes the error object from `http` to the `handleError()` method. The `handleError` method transforms the error into a developer-friendly message, @@ -486,36 +312,20 @@ logs it to the console, and returns the message in a new, failed Observable via {@a subscribe} - - {@a hero-list-component} - -

- HeroListComponent error handling -

- - - +### **HeroListComponent** error handling Back in the `HeroListComponent`, in `heroService.getHeroes()`, the `subscribe` function has a second function parameter to handle the error message. It sets an `errorMessage` variable that's bound conditionally in the `HeroListComponent` template. - - - - - +
- - -Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value. Afterward, remember to restore it. - - + Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value. Afterward, remember to restore it.
@@ -523,7 +333,6 @@ Want to see it fail? In the `HeroService`, reset the api endpoint to a bad value {@a update} {@a post} - ## Send data to the server So far you've seen how to retrieve data from a remote location using an HTTP service. @@ -532,12 +341,7 @@ Now you'll add the ability to create new heroes and save them in the backend. You'll write a method for the `HeroListComponent` to call, a `create()` method, that takes just the name of a new hero and returns an `Observable` of `Hero`. It begins like this: - - - - - - + To implement it, you must know the server's API for creating heroes. @@ -548,40 +352,26 @@ It expects the new hero data to arrive in the body of the request, structured like a `Hero` entity but without the `id` property. The body of the request should look like this: - { "name": "Windstorm" } - - The server generates the `id` and returns the entire `JSON` representation of the new hero including its generated id. The hero arrives tucked inside a response object with its own `data` property. Now that you know how the API works, implement `create()` as follows: + - - - - - - - - - - - + {@a headers} - ### Headers In the `headers` object, the `Content-Type` specifies that the body represents JSON. - Next, the `headers` object is used to configure the `options` object. The `options` object is a new instance of `RequestOptions`, a class that allows you to specify certain settings when instantiating a request. In this way, [headers](api/http/Headers) is one of the [RequestOptions](api/http/RequestOptions). @@ -591,62 +381,41 @@ In the `return` statement, `options` is the *third* argument of the `post()` met {@a json-results} - ### JSON results As with `getHeroes()`, use the `extractData()` helper to [extract the data](guide/http#extract-data) from the response. - - Back in the `HeroListComponent`, its `addHero()` method subscribes to the Observable returned by the service's `create()` method. When the data arrive it pushes the new hero object into its `heroes` array for presentation to the user. - - - - - - -

- Fall back to promises -

+ +{@a promises} +## Fall back to promises Although the Angular `http` client API returns an `Observable` you can turn it into a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). It's easy to do, and in simple cases, a Promise-based version looks much like the Observable-based version. +
- - -While Promises may be more familiar, Observables have many advantages. - + While Promises may be more familiar, Observables have many advantages.
- Here is a comparison of the `HeroService` using Promises versus Observables, highlighting just the parts that are different. - - - - - - - - - + + - - You can follow the Promise `then(this.extractData).catch(this.handleError)` pattern as in this example. @@ -660,51 +429,36 @@ The diagnostic *log to console* is just one more `then()` in the Promise chain. You have to adjust the calling component to expect a `Promise` instead of an `Observable`: - - - - - - - - - - + + - - The only obvious difference is that you call `then()` on the returned Promise instead of `subscribe`. Both methods take the same functional arguments. +
+ The less obvious but critical difference is that these two methods return very different results. + The Promise-based `then()` returns another Promise. You can keep chaining + more `then()` and `catch()` calls, getting a new promise each time. -The less obvious but critical difference is that these two methods return very different results. - -The Promise-based `then()` returns another Promise. You can keep chaining -more `then()` and `catch()` calls, getting a new promise each time. - -The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`. -It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again. -The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`. - -To understand the implications and consequences of subscriptions, -watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE) -or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises). + The `subscribe()` method returns a `Subscription`. A `Subscription` is not another `Observable`. + It's the end of the line for Observables. You can't call `map()` on it or call `subscribe()` again. + The `Subscription` object has a different purpose, signified by its primary method, `unsubscribe`. + To understand the implications and consequences of subscriptions, + watch [Ben Lesh's talk on Observables](https://www.youtube.com/watch?v=3LKMwkuK0ZE) + or his video course on [egghead.io](https://egghead.io/lessons/rxjs-rxjs-observables-vs-promises).
+{@a cors} -

- Cross-Origin Requests: Wikipedia example -

- - +## Cross-Origin Requests: Wikipedia example You just learned how to make `XMLHttpRequests` using the Angular Http service. This is the most common approach to server communication, but it doesn't work in all scenarios. @@ -716,109 +470,82 @@ This is called the [same-origin policy](https://en.wikipedia.org/wiki/Same-origi
- - -Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the -[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol. -If the server requires user credentials, enable them in the [request headers](guide/http#headers). - + Modern browsers do allow `XHR` requests to servers from a different origin if the server supports the + [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) protocol. + If the server requires user credentials, enable them in the [request headers](guide/http#headers).
- Some servers do not support CORS but do support an older, read-only alternative called [JSONP](https://en.wikipedia.org/wiki/JSONP). Wikipedia is one such server. +
- - -This [Stack Overflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP. + This [Stack Overflow answer](http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about/2067584#2067584) covers many details of JSONP.
- {@a search-wikipedia} - ### Search Wikipedia Here is a simple search that shows suggestions from Wikipedia as the user types in a text box: -
Wikipedia search app (v.1)
- - - Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. This example uses the latter. The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts you to `GET` requests. All other HTTP methods throw an error because `JSONP` is a read-only facility. As always, wrap the interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`. - - - - - - + The constructor expects Angular to inject its `Jsonp` service, which is available because `JsonpModule` is in the root `@NgModule` `imports` array in `app.module.ts`. + {@a query-parameters} ### Search parameters + The [Wikipedia "opensearch" API](https://www.mediawiki.org/wiki/API:Opensearch) expects four parameters (key/value pairs) to arrive in the request URL's query string. The keys are `search`, `action`, `format`, and `callback`. The value of the `search` key is the user-supplied search term to find in Wikipedia. The other three are the fixed values "opensearch", "json", and "JSONP_CALLBACK" respectively. +
- - -The `JSONP` technique requires that you pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. -The server uses that name to build a JavaScript wrapper function in its response, which Angular ultimately calls to extract the data. -All of this happens under the hood. + The `JSONP` technique requires that you pass a callback function name to the server in the query string: `callback=JSONP_CALLBACK`. + The server uses that name to build a JavaScript wrapper function in its response, which Angular ultimately calls to extract the data. + All of this happens under the hood.
- If you're looking for articles with the word "Angular", you could construct the query string by hand and call `jsonp` like this: - - - - - + In more parameterized examples you could build the query string with the Angular `URLSearchParams` helper: - - - - - + This time you call `jsonp` with *two* arguments: the `wikiUrl` and an options object whose `search` property is the `params` object. - - - - - + `Jsonp` flattens the `params` object into the same query string you saw earlier, sending the request to the server. + {@a wikicomponent} ### The WikiComponent @@ -826,11 +553,7 @@ to the server. Now that you have a service that can query the Wikipedia API, turn your attention to the component (template and class) that takes user input and displays search results. - - - - - + The template presents an `` element *search box* to gather search terms from the user, and calls a `search(term)` method after each `keyup` event. @@ -842,28 +565,26 @@ the app forwards the Observable result to the template (via `items`) where the ` in the `ngFor` handles the subscription. Read more about [async pipes](guide/pipes#async-pipe) in the [Pipes](guide/pipes) page. +
+ The [async pipe](guide/pipes#async-pipe) is a good choice in read-only components + where the component has no need to interact with the data. - -The [async pipe](guide/pipes#async-pipe) is a good choice in read-only components -where the component has no need to interact with the data. - -`HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list. + `HeroListComponent` can't use the pipe because `addHero()` pushes newly created heroes into the list.
- {@a wasteful-app} - ## A wasteful app The Wikipedia search makes too many calls to the server. It is inefficient and potentially expensive on mobile devices with limited data plans. ### 1. Wait for the user to stop typing + Presently, the code calls the server after every keystroke. It should only make requests when the user *stops typing*. Here's how it will work after refactoring: @@ -872,8 +593,6 @@ Here's how it will work after refactoring: Wikipedia search app (v.2) - - ### 2. Search when the search term changes Suppose a user enters the word *angular* in the search box and pauses for a while. @@ -893,10 +612,8 @@ in the original request order. In this example, the app must always display the results for the *http* search no matter which response arrives first. - {@a more-observables} - ## More fun with Observables You could make changes to the `WikipediaService`, but for a better @@ -905,30 +622,18 @@ with the help of some nifty Observable operators. Here's the `WikiSmartComponent`, shown next to the original `WikiComponent`: - - - - - - - - - - + + - - While the templates are virtually identical, there's a lot more RxJS in the "smart" version, starting with `debounceTime`, `distinctUntilChanged`, and `switchMap` operators, imported as [described above](guide/http#rxjs-library). - {@a create-stream} - ### Create a stream of search terms The `WikiComponent` passes a new search term directly to the `WikipediaService` after every keystroke. @@ -936,35 +641,21 @@ The `WikiComponent` passes a new search term directly to the `WikipediaService` The `WikiSmartComponent` class turns the user's keystrokes into an Observable _stream of search terms_ with the help of a `Subject`, which you import from RxJS: - - - - - + The component creates a `searchTermStream` as a `Subject` of type `string`. The `search()` method adds each new search box value to that stream via the subject's `next()` method. - - - - - - + {@a listen-for-search-terms} - ### Listen for search terms The `WikiSmartComponent` listens to the *stream of search terms* and processes that stream _before_ calling the service. - - - - - + * debounceTime waits for the user to stop typing for at least 300 milliseconds. @@ -984,11 +675,8 @@ The `switchMap` returns its own Observable that _combines_ all `WikipediaService re-arranges them in their original request order, and delivers to subscribers only the most recent search results. - {@a xsrf} - - ## Guarding against Cross-Site Request Forgery In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting @@ -1004,11 +692,8 @@ The server receives both the cookie and the header, compares them, and processes See the [XSRF topic on the Security page](guide/security#xsrf) for more information about XSRF and Angular's `XSRFStrategy` counter measures. - {@a override-default-request-options} - - ## Override default request headers (and other request options) Request options (such as headers) are merged into the @@ -1023,39 +708,23 @@ that sets the default options for the application. This sample creates a class that sets the default `Content-Type` header to JSON. It exports a constant with the necessary `RequestOptions` provider to simplify registration in `AppModule`. - - - - - - + Then it registers the provider in the root `AppModule`. - - - - +
- - -Remember to include this provider during setup when unit testing the app's HTTP services. + Remember to include this provider during setup when unit testing the app's HTTP services.
- After this change, the `header` option setting in `HeroService.create()` is no longer necessary, - - - - - - + You can confirm that `DefaultRequestOptions` is working by examing HTTP requests in the browser developer tools' network tab. If you're short-circuiting the server call with something like the [_in-memory web api_](guide/http#in-mem-web-api), @@ -1066,11 +735,8 @@ to verify the header is there. Individual requests options, like this one, take precedence over the default `RequestOptions`. It might be wise to keep the `create` request header setting for extra safety. - {@a in-mem-web-api} - - ## Appendix: Tour of Heroes _in-memory web api_ If the app only needed to retrieve data, you could get the heroes from a `heroes.json` file: @@ -1078,23 +744,15 @@ If the app only needed to retrieve data, you could get the heroes from a `heroes
- - -You wrap the heroes array in an object with a `data` property for the same reason that a data server does: -to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) -posed by top-level JSON arrays. + You wrap the heroes array in an object with a `data` property for the same reason that a data server does: + to mitigate the [security risk](http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk) + posed by top-level JSON arrays.
- - You'd set the endpoint to the JSON file like this: - - - - - + The *get heroes* scenario would work, but since the app can't save changes to a JSON file, it needs a web API server. @@ -1104,52 +762,35 @@ it substitutes the Angular _in-memory web api_ simulator for the actual XHR back
+ The in-memory web api is not part of Angular _proper_. + It's an optional service in its own + angular-in-memory-web-api + library installed with npm (see `package.json`). - -The in-memory web api is not part of Angular _proper_. -It's an optional service in its own -angular-in-memory-web-api -library installed with npm (see `package.json`). - -See the -README file -for configuration options, default behaviors, and limitations. - + See the + README file + for configuration options, default behaviors, and limitations.
- The in-memory web API gets its data from a custom application class with a `createDb()` method that returns a map whose keys are collection names and whose values are arrays of objects in those collections. Here's the class for this sample, based on the JSON data: - - - - - + Ensure that the `HeroService` endpoint refers to the web API: - - - - - - + Finally, redirect client HTTP requests to the in-memory web API by adding the `InMemoryWebApiModule` to the `AppModule.imports` list. At the same time, call its `forRoot()` configuration method with the `HeroData` class. - - - - - + ### How it works @@ -1160,35 +801,26 @@ Using standard Angular provider registration techniques, the `InMemoryWebApiModu replaces the default `XHRBackend` service with its own in-memory alternative. At the same time, the `forRoot` method initializes the in-memory web API with the *seed data* from the mock hero dataset. +
- - -The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_, -while setting the metadata for the root `AppModule`. Don't call it again. + The `forRoot()` method name is a strong reminder that you should only call the `InMemoryWebApiModule` _once_, + while setting the metadata for the root `AppModule`. Don't call it again.
- Here is the final, revised version of src/app/app.module.ts, demonstrating these steps. - - - - - +
- - -Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that -the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others. + Import the `InMemoryWebApiModule` _after_ the `HttpModule` to ensure that + the `XHRBackend` provider of the `InMemoryWebApiModule` supersedes all others.
- See the full source code in the . diff --git a/aio/content/tutorial/toh-pt6.md b/aio/content/tutorial/toh-pt6.md index 890106bfb2..3cf1b20bb0 100644 --- a/aio/content/tutorial/toh-pt6.md +++ b/aio/content/tutorial/toh-pt6.md @@ -1,12 +1,4 @@ -@title -HTTP - -@intro -Convert the service and components to use Angular's HTTP service. - -@description - - +# HTTP In this page, you'll make the following improvements. @@ -18,39 +10,25 @@ You'll teach the app to make corresponding HTTP calls to a remote server's web A When you're done with this page, the app should look like this . - - ## Where you left off In the [previous page](tutorial/toh-pt5), you learned to navigate between the dashboard and the fixed heroes list, editing a selected hero along the way. That's the starting point for this page. - ## Keep the app transpiling and running Enter the following command in the terminal window: - npm start - - - This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. The command simultaneously launches the app in a browser and refreshes the browser when the code changes. - You can keep building the Tour of Heroes without pausing to recompile or refresh the browser. - - -

- Providing HTTP Services -

- - +## Providing HTTP Services The `HttpModule` is not a core Angular module. `HttpModule` is Angular's optional approach to web access. It exists as a separate add-on module called `@angular/http` @@ -58,28 +36,20 @@ and is shipped in a separate script file as part of the Angular npm package. You're ready to import from `@angular/http` because `systemjs.config` configured *SystemJS* to load that library when you need it. - ## Register for HTTP services - The app will depend on the Angular `http` service, which itself depends on other supporting services. The `HttpModule` from the `@angular/http` library holds providers for a complete set of HTTP services. To allow access to these services from anywhere in the app, add `HttpModule` to the `imports` list of the `AppModule`. - - - - - - + Notice that you also supply `HttpModule` as part of the *imports* array in root NgModule `AppModule`. - - ## Simulate the web API + We recommend registering app-wide services in the root `AppModule` *providers*. @@ -89,93 +59,59 @@ a mock service, the *in-memory web API*. Update src/app/app.module.ts with this version, which uses the mock service: - - - - - - + Rather than require a real API server, this example simulates communication with the remote server by adding the -InMemoryWebApiModule +InMemoryWebApiModule to the module `imports`, effectively replacing the `Http` client's XHR backend service with an in-memory alternative. - - - - - - + The `forRoot()` configuration method takes an `InMemoryDataService` class that primes the in-memory database. Add the file `in-memory-data.service.ts` in `app` with the following content: - - - - - - + This file replaces `mock-heroes.ts`, which is now safe to delete. Added hero "Zero" to confirm that the data service can handle a hero with `id==0`. +
+ The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes. + Don't worry about the details of this backend substitution; you can + skip it when you have a real web API server. - -The in-memory web API is only useful in the early stages of development and for demonstrations such as this Tour of Heroes. -Don't worry about the details of this backend substitution; you can -skip it when you have a real web API server. - -Read more about the in-memory web API in the -[Appendix: Tour of Heroes in-memory web api](guide/http#in-mem-web-api) -section of the [HTTP Client](guide/http#in-mem-web-api) page. - + Read more about the in-memory web API in the + [Appendix: Tour of Heroes in-memory web api](guide/http#in-mem-web-api) + section of the [HTTP Client](guide/http#in-mem-web-api) page.
- - - ## Heroes and HTTP In the current `HeroService` implementation, a Promise resolved with mock heroes is returned. - - - - - - + This was implemented in anticipation of ultimately fetching heroes with an HTTP client, which must be an asynchronous operation. Now convert `getHeroes()` to use HTTP. - - - - - - + Update the import statements as follows: - - - - - - + Refresh the browser. The hero data should successfully load from the mock server. -

HTTP Promise

+{@a http-promise} +### HTTP Promise The Angular `http.get` returns an RxJS `Observable`. *Observables* are a powerful way to manage asynchronous data flows. @@ -183,12 +119,7 @@ You'll read about [Observables](tutorial/toh-pt6#observables) later in this page For now, you've converted the `Observable` to a `Promise` using the `toPromise` operator. - - - - - - + The Angular `Observable` doesn't have a `toPromise` operator out of the box. @@ -196,35 +127,22 @@ There are many operators like `toPromise` that extend `Observable` with useful c To use those capabilities, you have to add the operators themselves. That's as easy as importing them from the RxJS library like this: - - - - - +
- - -You'll add more operators, and learn why you must do so, [later in this tutorial](tutorial/toh-pt6#rxjs-imports). - + You'll add more operators, and learn why you must do so, [later in this tutorial](tutorial/toh-pt6#rxjs-imports).
- ### Extracting the data in the *then* callback In the *Promise*'s `then()` callback, you call the `json` method of the HTTP `Response` to extract the data within the response. - - - - - - + The response JSON has a single `data` property, which holds the array of heroes that the caller wants. @@ -233,17 +151,13 @@ So you grab that array and return it as the resolved Promise value.
- - -Note the shape of the data that the server returns. -This particular in-memory web API example returns an object with a `data` property. -Your API might return something else. Adjust the code to match your web API. - + Note the shape of the data that the server returns. + This particular in-memory web API example returns an object with a `data` property. + Your API might return something else. Adjust the code to match your web API.
- The caller is unaware that you fetched the heroes from the (mock) server. It receives a Promise of *heroes* just as it did before. @@ -251,22 +165,12 @@ It receives a Promise of *heroes* just as it did before. At the end of `getHeroes()`, you `catch` server failures and pass them to an error handler. - - - - - - + This is a critical step. You must anticipate HTTP failures, as they happen frequently for reasons beyond your control. - - - - - - + This demo service logs the error to the console; in real life, you would handle the error in code. For a demo, this works. @@ -274,7 +178,6 @@ you would handle the error in code. For a demo, this works. The code also includes an 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 When the `HeroDetailComponent` asks the `HeroService` to fetch a hero, @@ -285,11 +188,7 @@ Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (such as Update the `HeroService.getHero()` method to make a _get-by-id_ request: - - - - - + This request is almost the same as `getHeroes()`. The hero id in the URL identifies which hero the server should update. @@ -305,8 +204,6 @@ You won't have to update any of the components that call them. Now it's time to add the ability to create and delete heroes. - - ## Updating hero details Try editing a hero's name in the hero detail view. @@ -324,35 +221,19 @@ 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()`. - - - - - - + Add the following `save()` method, which persists hero name changes using the hero service `update()` method and then navigates back to the previous view. - - - - - - + ### Add a hero service _update()_ method The overall structure of the `update()` method is similar to that of `getHeroes()`, but it uses an HTTP `put()` to persist server-side changes. - - - - - - - + To identify which hero the server should update, the hero `id` is encoded in the URL. The `put()` body is the JSON string encoding of the hero, obtained by @@ -362,8 +243,6 @@ calling `JSON.stringify`. The body content type Refresh the browser, change a hero name, save your change, and click the browser Back button. Changes should now persist. - - ## Add the ability to add heroes To add a hero, the app needs the hero's name. You can use an `input` @@ -372,38 +251,22 @@ element paired with an add button. Insert the following into the heroes component HTML, just after the heading: - - - - - - + In response to a click event, call the component's click handler and then clear the input field so that it's ready for another name. - - - - - - + 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 the array. Implement the `create()` method in the `HeroService` class. - - - - - + Refresh the browser and create some heroes. - - ## Add the ability to delete a hero Each hero in the heroes view should have a delete button. @@ -411,21 +274,11 @@ Each hero in the heroes view should have a delete button. Add the following button element to the heroes component HTML, after the hero name in the repeated `
  • ` element. - - - - - - + The `
  • ` element should now look like this: - - - - - - + In addition to calling the component's `delete()` method, the delete button's click handler code stops the propagation of the click event—you @@ -434,57 +287,34 @@ select the hero that the user will delete. The logic of the `delete()` handler is a bit trickier: - - - - - - + Of course you 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. - To place the delete button at the far right of the hero entry, add this CSS: - - - - - - + ### Hero service _delete()_ method Add the hero service's `delete()` method, which uses the `delete()` HTTP method to remove the hero from the server: - - - - - - + Refresh the browser and try the new delete functionality. - -
    - -
    - - - ## Observables - Each `Http` service method returns an `Observable` of HTTP `Response` objects. The `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller. This section shows you how, when, and why to return the `Observable` directly. ### Background + An *Observable* is a stream of events that you can process with array-like operators. Angular core has basic support for observables. @@ -499,7 +329,6 @@ Converting to a Promise is often a good choice. You typically ask `http.get()` t When you receive the data, you're done. The calling component can easily consume a single result in the form of a Promise. - But requests aren't always done only once. You may start one request, cancel it, and make a different request before the server has responded to the first request. @@ -508,17 +337,13 @@ A *request-cancel-new-request* sequence is difficult to implement with `Promise` easy with `Observable`s. ### Add the ability to search by name + You're going to add a *hero search* feature to the Tour of Heroes. As the user types a name into a search box, you'll make repeated HTTP requests for heroes filtered by that name. Start by creating `HeroSearchService` that sends search queries to the server's web API. - - - - - - + The `http.get()` call in `HeroSearchService` is similar to the one in the `HeroService`, although the URL now has a query string. @@ -530,27 +355,17 @@ to extract heroes from the response data. RxJS operator chaining makes response processing easy and readable. See the [discussion below about operators](tutorial/toh-pt6#rxjs-imports). - ### HeroSearchComponent Create a `HeroSearchComponent` that calls the new `HeroSearchService`. The component template is simple—just a text box and a list of matching search results. - - - - - - + Also, add styles for the new component. - - - - - + As the user types in the search box, a *keyup* event binding calls the component's `search()` method with the new search box value. @@ -563,50 +378,32 @@ The `async` pipe subscribes to the `Observable` and produces the array of heroes Create the `HeroSearchComponent` class and metadata. - - - - - - + #### Search terms Focus on the `searchTerms`: - - - - - - + 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()`. - {@a ngoninit} - #### Initialize the *heroes* property (*ngOnInit*) A `Subject` is also an `Observable`. You can turn the stream of search terms into a stream of `Hero` arrays and assign the result to the `heroes` property. - - - - - - + Passing every user keystroke directly to the `HeroSearchService` would create an excessive amount of HTTP requests, taxing server resources and burning through the cellular network data plan. - Instead, you can chain `Observable` operators that reduce the request flow to the string `Observable`. You'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how: @@ -619,37 +416,31 @@ It cancels and discards previous search observables, returning only the latest s
    + With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html) + (formerly known as `flatMapLatest`), + every qualifying key event can trigger an `http()` 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. -With the [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html) -(formerly known as `flatMapLatest`), -every qualifying key event can trigger an `http()` 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. + If the search text is empty, the `http()` method call is also short circuited + and an observable containing an empty array is returned. -`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. - -If the search text is empty, the `http()` method call is also short circuited -and an observable containing an empty array is returned. - -Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable -doesn't actually abort a pending HTTP request. -For now, unwanted results are discarded. + Note that until the service supports that feature, _canceling_ the `HeroSearchService` Observable + doesn't actually abort a pending HTTP request. + For now, unwanted results are discarded.
    - * `catch` intercepts a failed observable. The simple example prints the error to the console; a real life app would do better. Then to clear the search result, you return an observable containing an empty array. - {@a rxjs-imports} - ### Import RxJS operators Most RxJS operators are not included in Angular's base `Observable` implementation. @@ -658,12 +449,7 @@ The base implementation includes only what Angular itself requires. When you need more RxJS features, extend `Observable` by *importing* the libraries in which they are defined. Here are all the RxJS imports that _this_ component needs: - - - - - - + The `import 'rxjs/add/...'` syntax may be unfamiliar. It's missing the usual list of symbols between the braces: `{...}`. @@ -672,185 +458,70 @@ You 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. - ### Add the search component to the dashboard Add the hero search HTML element to the bottom of the `DashboardComponent` template. - - - - - - + Finally, import `HeroSearchComponent` from hero-search.component.ts and add it to the `declarations` array. - - - - - + 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. -
    Hero Search Component
    - - - ## App structure and code Review the sample source code in the for this page. Verify that you have the following structure: -
    - -
    - angular-tour-of-heroes -
    - +
    angular-tour-of-heroes
    - -
    - src -
    - +
    src
    - -
    - app -
    - +
    app
    - -
    - app.component.ts -
    - -
    - app.component.css -
    - -
    - app.module.ts -
    - -
    - app-routing.module.ts -
    - -
    - dashboard.component.css -
    - -
    - dashboard.component.html -
    - -
    - dashboard.component.ts -
    - -
    - hero.ts -
    - -
    - hero-detail.component.css -
    - -
    - hero-detail.component.html -
    - -
    - hero-detail.component.ts -
    - -
    - hero-search.component.html (new) -
    - -
    - hero-search.component.css (new) -
    - -
    - hero-search.component.ts (new) -
    - -
    - hero-search.service.ts (new) -
    - -
    - hero.service.ts -
    - -
    - heroes.component.css -
    - -
    - heroes.component.html -
    - -
    - heroes.component.ts -
    - -
    - in-memory-data.service.ts (new) -
    - +
    app.component.ts
    +
    app.component.css
    +
    app.module.ts
    +
    app-routing.module.ts
    +
    dashboard.component.css
    +
    dashboard.component.html
    +
    dashboard.component.ts
    +
    hero.ts
    +
    hero-detail.component.css
    +
    hero-detail.component.html
    +
    hero-detail.component.ts
    +
    hero-search.component.html (new)
    +
    hero-search.component.css (new)
    +
    hero-search.component.ts (new)
    +
    hero-search.service.ts (new)
    +
    hero.service.ts
    +
    heroes.component.css
    +
    heroes.component.html
    +
    heroes.component.ts
    +
    in-memory-data.service.ts (new)
    - -
    - main.ts -
    - -
    - index.html -
    - -
    - styles.css -
    - -
    - systemjs.config.js -
    - -
    - tsconfig.json -
    - +
    main.ts
    +
    index.html
    +
    styles.css
    +
    systemjs.config.js
    +
    tsconfig.json
    - -
    - node_modules ... -
    - -
    - package.json -
    - +
    node_modules ...
    +
    package.json
    -
    - - - ## Home Stretch You're at the end of your journey, and you've accomplished a lot. @@ -865,68 +536,24 @@ Here are the files you added or changed in this page. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + - ## Next step That concludes the "Tour of Heroes" tutorial.