AngularIo - BuiltInFeatures - HttpClient 1
AngularIo - BuiltInFeatures - HttpClient 1
AngularIo_BuiltInFeatures_HttpClient 1
Most front-end applications need to communicate with a server over the HTTP protocol, in order to download or upload data and
access other back-end services. Angular provides a simplified client HTTP API for Angular applications, the HttpClient service class in
@angular/common/http.
TypeScript programming
Usage of the HTTP protocol
Angular app-design fundamentals, as described in Angular Concepts
Observable techniques and operators. See the Observables guide.
Setup for server communication
Before you can use HttpClient, you need to import the Angular HttpClientModule. Most apps do so in the root AppModule.
app/app.module.ts (excerpt)
content_copy
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.
app/config/config.service.ts (excerpt)
content_copy
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.
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.
The get() method takes two arguments; the endpoint URL from which to fetch, and an options object that you can use to configure
the request.
content_copy
options: {
headers?: HttpHeaders | {[header: string]: string | string[]},
observe?: 'body' | 'events' | 'response',
params?: HttpParams|{[param: string]: string | string[]},
reportProgress?: boolean,
responseType?: 'arraybuffer'|'blob'|'json'|'text',
withCredentials?: boolean,
}
Important options include the observe and responseType properties.
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.
assets/config.json
content_copy
{
"heroesUrl": "api/heroes",
"textfile": "assets/textfile.txt"
}
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.
getConfig() {
return this.http.get(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.
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 will actually respond 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.
content_copy
export interface Config {
heroesUrl: string;
textfile: string;
}
Next, 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:
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.
content_copy
.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.
content_copy
options: {
...
observe?: 'body' | 'events' | 'response',
...
responseType?: 'arraybuffer'|'blob'|'json'|'text',
...
}
This can cause confusion. For example:
content_copy
// this works
client.get('/foo', {responseType: 'text'})
Use as const to let TypeScript know that you really do mean to use a constant string type:
content_copy
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:
content_copy
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:
app/config/config.component.ts (showConfigResponse)
content_copy
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)}`);
Angular JSONP requests return an Observable. Follow the pattern for subscribing to observables and use the RxJS map operator to
transform the response before using the async pipe to manage the results.
In Angular, use JSONP by including HttpClientJsonpModule in the NgModule imports. In the following example, the searchHeroes()
method uses a JSONP request to query for heroes whose names contain the search term.
content_copy
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable {
term = term.trim();
app/downloader/downloader.service.ts (getTextFile)
content_copy
getTextFile(filename: string) {
// The Observable returned by get() is of type Observable<string>
// because a text response was specified.
// There's no need to pass a <string> type parameter to get().
return this.http.get(filename, {responseType: 'text'})
.pipe(
tap( // Log the result or error
data => this.log(filename, data),
error => this.logError(filename, error)
)
);
}
HttpClient.get() returns a string rather than the default JSON because of the responseType option.
The RxJS tap operator (as in "wiretap") lets the code inspect both success and error values passing through the observable without
disturbing them.
A download() method in the DownloaderComponent initiates the request by subscribing to the service method.
app/downloader/downloader.component.ts (download)
content_copy
download() {
this.downloaderService.getTextFile('assets/textfile.txt')
.subscribe(results => this.contents = results);
}