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.
Before you can use HttpClient
, you need to import the Angular HttpClientModule
. Most apps do so in the root AppModule
.
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ BrowserModule, // import HttpClientModule after BrowserModule. HttpClientModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent ] }) export class AppModule {}
You can then inject the HttpClient
service as a dependency of an application class, as shown in the following ConfigService
example.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class ConfigService { constructor(private http: HttpClient) { } }
The HttpClient
service makes use of observables for all transactions. You must import the RxJS observable and operator symbols that appear in the example snippets. These ConfigService
imports are typical.
import { Observable, throwError } from 'rxjs'; import { catchError, retry } from 'rxjs/operators';
You can run the live example that accompanies this guide.
The sample app does not require a data server. It relies on the 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.
Use the 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.
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 theheaders
option property.Use the
params
property to configure a request with [TTP URL parameters, and thereportProgress
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.
{ "heroesUrl": "api/heroes", "textfile": "assets/textfile.txt", "date": "2020-01-29" }
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 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.
configUrl = 'assets/config.json'; getConfig() { return this.http.get<Config>(this.configUrl); }
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.
showConfig() { this.configService.getConfig() .subscribe((data: Config) => this.config = { heroesUrl: data.heroesUrl, textfile: data.textfile, date: data.date, }); }
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.
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.
export interface Config { heroesUrl: string; textfile: string; date: any; }
Next, specify that interface as the HttpClient.get()
call's type parameter in the service.
getConfig() { // now returns an Observable of Config return this.http.get<Config>(this.configUrl); }
When you pass an interface as a type parameter to the
HttpClient.get()
method, use the RxJSmap
operator to transform the response data as needed by the UI. You can then pass the transformed data to the async pipe.
The callback in the updated component method receives a typed data object, which is easier and safer to consume:
config: Config | undefined; showConfig() { this.configService.getConfig() // clone the data object, using its known Config shape .subscribe((data: Config) => this.config = { ...data }); }
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
typesThe 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);
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:
getConfigResponse(): Observable<HttpResponse<Config>> { return this.http.get<Config>( this.configUrl, { observe: 'response' }); }
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:
showConfigResponse() { this.configService.getConfigResponse() // resp is of type `HttpResponse<Config>` .subscribe(resp => { // display its headers const keys = resp.headers.keys(); this.headers = keys.map(key => `${key}: ${resp.headers.get(key)}`); // access the body directly, which is typed as `Config`. this.config = { ...resp.body! }; }); }
As you can see, the response object has a body
property of the correct type.
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/http-server-communication