**Unit Testing Code Does Not Compile** The compilation error is: ``` The compilation error is ./src/testing/http-client.spec.ts - Error: Module build failed (from ./node_modules/@ngtools/webpack/src/ivy/index.js): Error: /home/projects/obk3vc--run/src/testing/http-client.spec.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property. ``` I’m not sure what to say about unit testing HTTP in a full Standalone app. Is it different? _This is the only known remaining defect in this conversion of HTTP to Standalone._ **Edited content of `http-request-data-from-server.md`** The current version of this page is confusing. In particular * It tells readers they **should always unsubscribe** from the HttpClient method calls. This is *not true* and this example doesn't even do it. I replaced this instruction with more nuanced advice and an explanation of why it is OK to not unsubscribe to HttpClient methods. * There is a "helpful" note about using the RxJS `map` operator to transform the response. This is *not "helpfulf"*. It is *confusing* because the sample doesn't use `map` anywhere. It was unnecessary here, even if it might be helpful elsewhere. I removed this note. * The "Requesting a typed response" section seemed unclear to me, particularly because the guide begins with a `get` request that already has the `Config` return type specification. My revision attempts to make this more clear. * The bold "callout" about the `observe` and `responseType` options appears out of nowhere after "Requesting a typed response". It's disconcerting at best. I moved it to the bottom of the page and linked to it from the `options` discussion at the top. I made a few other revisions that I hope improve the readability of this page. **Corrected `http-make-jsonp-request.md`** The JSONP example, handwritten in the guide page, would not have compiled. I added one that does to `heroes.service.ts` and displayed it on this page. **Corrected `http-handle-request-errors.md` This page ended with a section called "Sending data to a server" that introduces PUT, POST, and DELETE. These features have nothing to do with error handling and the verbiage here duplicates the opening paragraphs of the next topic which does: "Send data to a server". So I deleted this section from the error handling guide page. **Archived http-setup-server-communication.md** `http-setup-server-communication.md` appears to be the original long document that has since been divided over the other pages in this folder. It shouldn’t be in the reader’s flow. I did update it for Standalone. But I also removed it from left-nav and marked as archived. PR Close #51400
10 KiB
HTTP: Request data from a server
Use the HttpClient.get() method to fetch data from a server.
This asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received.
The get(url, options) method takes two arguments; the string endpoint URL from which to fetch, and an optional options object 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 desired format of the returned data
To better understand the observe and responseType option types, see below.
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 HTTP 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 most get() calls - and most of the following examples - do not pass the options object.
Later sections show some of the additional option possibilities.
Handle data access in a service class
The example conforms to the best practice for maintainable solutions by isolating the data-access functionality in a re-usable injectable service separate from the component.
The ConfigService fetches the JSON file using the HttpClient.get() method.
Notice that get was called
- without an options value because the server endpoint returns JSON and JSON is the default data format.
- with a generic,
Config, that indicates the data return type; you'll learn why shortly.
In addition to fetching data, the service can post-process the data, add error handling, and add retry logic.
Present the data in the component
The ConfigComponent injects the ConfigService in its constructor and offers a showConfig method, which calls the service's getConfig method.
Because the service's getConfig method returns an Observable of configuration data, the component subscribes to the method's return value.
If you didn't subscribe, the service would not have issued an HTTP request and there would be no config data to display. You will understand why shortly.
This example 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.
In general, you should unsubscribe from an observable when a component is destroyed.
You don't have to unsubscribe from HttpClient observables because they unsubscribe automatically after the server request responds or times out. Most developers choose not to unsubscribe. None of this guide's examples unsubscribe.
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.
Suppose you made the get call without specifying the return type like this:
The return type would be Object, To access its properties you would have to explicitly convert them with as any like this:
It's safer and less clumsy if the returned object has the desired type.
Begin by defining 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.
Now, specify that interface as the HttpClient.get() call's type parameter in the service.
The callback in the updated component method receives a typed data object, which is easier and safer to consume:
You can go a step further and clone the result directly into the component's config property with destructuring.
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.
The observe and responseType options
The types of the observe and responseType 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);
@reviewed 2023-08-18