From 19a4cdec7db297e208fee96eef1049777b49abc7 Mon Sep 17 00:00:00 2001 From: Virginia Dooley Date: Wed, 2 Nov 2022 00:47:39 +0000 Subject: [PATCH] docs: New document extracted from the original Communicating with backend services using HTTP document, which is to be retired. (#47937) PR Close #47937 --- .pullapprove.yml | 1 + .../guide/http-server-communication.md | 254 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 aio/content/guide/http-server-communication.md diff --git a/.pullapprove.yml b/.pullapprove.yml index ddd01beebed..95fb1c38a25 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -417,6 +417,7 @@ groups: 'aio/content/guide/http-request-data-from-server.md', 'aio/content/guide/http-security-xsrf-protection.md', 'aio/content/guide/http-send-data-to-server.md', + 'aio/content/guide/http-server-communication.md', 'aio/content/guide/http-setup-server-communication.md', 'aio/content/guide/http-test-requests.md', 'aio/content/guide/http-track-show-request-progress.md', diff --git a/aio/content/guide/http-server-communication.md b/aio/content/guide/http-server-communication.md new file mode 100644 index 00000000000..eb7c11ff284 --- /dev/null +++ b/aio/content/guide/http-server-communication.md @@ -0,0 +1,254 @@ +# HTTP Server communication + +Most front-end applications need to communicate with a server over the HTTP protocol, to download or upload data and access other back-end services. + +## Setup for server communication + +Before you can use `HttpClient`, you need to import the Angular `HttpClientModule`. +Most apps do so in the root `AppModule`. + + + +You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` example. + + + +The `HttpClient` service makes use of [observables](guide/glossary#observable "Observable definition") for all transactions. +You must import the RxJS observable and operator symbols that appear in the example snippets. +These `ConfigService` imports are typical. + + + +
+ +You can run the that accompanies this guide. + +The sample app does not require a data server. +It relies on the [Angular *in-memory-web-api*](https://github.com/angular/angular/tree/main/packages/misc/angular-in-memory-web-api), which replaces the *HttpClient* module's `HttpBackend`. +The replacement service simulates the behavior of a REST-like backend. + +Look at the `AppModule` *imports* to see how it is configured. + +
+ +## Requesting data from a server + +Use the [`HttpClient.get()`](api/common/http/HttpClient#get) method to fetch data from a server. +The asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received. +The return type varies based on the `observe` and `responseType` values that you pass to the call. + +The `get()` method takes two arguments; the endpoint URL from which to fetch, and an *options* object that is used to configure the request. + + + +options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body' | 'events' | 'response', + params?: HttpParams|{[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, +} + + + +Important options include the *observe* and *responseType* properties. + +* The *observe* option specifies how much of the response to return +* The *responseType* option specifies the format in which to return data + +
+ +Use the `options` object to configure various other aspects of an outgoing request. +In adding headers, for example, the service set the default headers using the `headers` option property. + +Use the `params` property to configure a request with [TTP URL parameters, and the `reportProgress` option to listen for progress events when transferring large amounts of data. + +
+ +Applications often request JSON data from a server. +In the `ConfigService` example, the app needs a configuration file on the server, `config.json`, that specifies resource URLs. + + + +To fetch this kind of data, the `get()` call needs the following options: `{observe: 'body', responseType: 'json'}`. +These are the default values for those options, so the following examples do not pass the options object. +Later sections show some of the additional option possibilities. + + + +The example conforms to the best practices for creating scalable solutions by defining a re-usable [injectable service](guide/glossary#service "service definition") to perform the data-handling functionality. +In addition to fetching data, the service can post-process the data, add error handling, and add retry logic. + +The `ConfigService` fetches this file using the `HttpClient.get()` method. + + + +The `ConfigComponent` injects the `ConfigService` and calls the `getConfig` service method. + +Because the service method returns an `Observable` of configuration data, the component *subscribes* to the method's return value. +The subscription callback performs minimal post-processing. +It copies the data fields into the component's `config` object, which is data-bound in the component template for display. + + + + + +### Starting the request + +For all `HttpClient` methods, the method doesn't begin its HTTP request until you call `subscribe()` on the observable the method returns. + +This is true for *all* `HttpClient` *methods*. + +
+ +You should always unsubscribe from an observable when a component is destroyed. + +
+ +All observables returned from `HttpClient` methods are *cold* by design. +Execution of the HTTP request is *deferred*, letting you extend the observable with additional operations such as `tap` and `catchError` before anything actually happens. + +Calling `subscribe()` triggers execution of the observable and causes `HttpClient` to compose and send the HTTP request to the server. + +Think of these observables as *blueprints* for actual HTTP requests. + +
+ +In fact, each `subscribe()` initiates a separate, independent execution of the observable. +Subscribing twice results in two HTTP requests. + + + +const req = http.get<Heroes>('/api/heroes'); +// 0 requests made - .subscribe() not called. +req.subscribe(); +// 1 request made. +req.subscribe(); +// 2 requests made. + + + +
+ + + +### Requesting a typed response + +Structure your `HttpClient` request to declare the type of the response object, to make consuming the output easier and more obvious. +Specifying the response type acts as a type assertion at compile time. + +
+ +Specifying the response type is a declaration to TypeScript that it should treat your response as being of the given type. +This is a build-time check and doesn't guarantee that the server actually responds with an object of this type. +It is up to the server to ensure that the type specified by the server API is returned. + +
+ +To specify the response object type, first define an interface with the required properties. +Use an interface rather than a class, because the response is a plain object that cannot be automatically converted to an instance of a class. + + + +Next, specify that interface as the `HttpClient.get()` call's type parameter in the service. + + + +
+ +When you pass an interface as a type parameter to the `HttpClient.get()` method, use the [RxJS `map` operator](guide/rx-library#operators) to transform the response data as needed by the UI. +You can then pass the transformed data to the [async pipe](api/common/AsyncPipe). + +
+ +The callback in the updated component method receives a typed data object, which is easier and safer to consume: + + + +To access properties that are defined in an interface, you must explicitly convert the plain object you get from the JSON to the required response type. +For example, the following `subscribe` callback receives `data` as an Object, and then type-casts it in order to access the properties. + + + +.subscribe(data => this.config = { + heroesUrl: (data as any).heroesUrl, + textfile: (data as any).textfile, +}); + + + + + +
+ +
observe and response types
+ +The types of the `observe` and `response` options are *string unions*, rather than plain strings. + + + +options: { + … + observe?: 'body' | 'events' | 'response', + … + responseType?: 'arraybuffer'|'blob'|'json'|'text', + … +} + + + +This can cause confusion. +For example: + + + +// this works +client.get('/foo', {responseType: 'text'}) + +// but this does NOT work +const options = { + responseType: 'text', +}; +client.get('/foo', options) + + + +In the second case, TypeScript infers the type of `options` to be `{responseType: string}`. +The type is too wide to pass to `HttpClient.get` which is expecting the type of `responseType` to be one of the *specific* strings. +`HttpClient` is typed explicitly this way so that the compiler can report the correct return type based on the options you provided. + +Use `as const` to let TypeScript know that you really do mean to use a constant string type: + + + +const options = { + responseType: 'text' as const, +}; +client.get('/foo', options); + + + +
+ +### Reading the full response + +In the previous example, the call to `HttpClient.get()` did not specify any options. +By default, it returned the JSON data contained in the response body. + +You might need more information about the transaction than is contained in the response body. +Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow. + +Tell `HttpClient` that you want the full response with the `observe` option of the `get()` method: + + + +Now `HttpClient.get()` returns an `Observable` of type `HttpResponse` rather than just the JSON data contained in the body. + +The component's `showConfigResponse()` method displays the response headers as well as the configuration: + + + +As you can see, the response object has a `body` property of the correct type. + +@reviewed 2023-02-27