-
Notifications
You must be signed in to change notification settings - Fork 1
Angular ~ Http
- Introduction
- The
HttpClientModule
in@angular/common/http
offers a simplified client HTTP API for Angular applications.
- The
- Request
- Methods
- get
- post
- put
- delete
- request
- To deal with progress events
- It takes
new HttpRequest('<method>', '<url>', {<options>})
as a parameters
- Configuration ({})
- observe: 'response/body/events'
- responseType: 'text/json/blob'
- headers: new HttpHeaders().set('Authorization', 'access_token')
- params: new HttpParams().set('customProp', 'foo')
- reportProgress: true/false
- withCredentials: true/false
- Methods
- Response
- Observable/Promise is object to deal with asynchronous operations
- Transform response
- Headers
- Body
- Events
- Error Handler
- Interceptor
- Introduction
- Interceptors provide a mechanism to intercept every outgoing request or incoming response.
- Create an interceptor
- HttpInterceptor interface
- Intercept request
- Intercept response
- Register an interceptor
- Introduction
In JavaScript making HTTP requests is an asynchronous operation. It just sends the HTTP request to the API and when the API responds later then we get notified and we can start processing the response.
Angular comes with its own HTTP client library built on top of the XMLHttpRequest interface which we can use to make those calls.
It supports strong typing of request and response objects and request and response interceptor.
We can use Promises
or Observables
to handle these asynchronous operations.
The http client is a service which lives in the @angular/common/http
module along side a number of other classes which are helpful when working with http.
We inject the Http client library into our classes, it’s a dependency that needs to be configured in Angulars DI framework.
Rather than separately setup each provider for all the different parts of the Http client library we can instead import the HttpClientModule
and add to our NgModule
imports list. Importing HttpClientModule
into our NgModule
configures our NgModules
injector with all the providers needed to use Http in our app.
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [ HttpClientModule ]
});
We make requests for all the HTTP verbs by calling matching functions on our Http client library.
To perform a GET request we simply call the get
function on our http client.
To perform a DELETE request we just call the delete
function.
To perform a POST request we call the post
function. The second parameter to post
is an object which will be passed as the payload for the request.
To perform a PUT request we just call the put
function. It works in exactly the same was the post
function.
All these methods are in fact just syntactic sugar, and they all call the method request
under the hood.
We can pass a set of optional query parameters as an argument as well. This is in the format of an object like structure but to help in creating our query params object we can use the helper HttpParams
class.
By default, the HttpClient
returns the body of the response. You can pass in an object with an observe
key set to a value of response
to get the full response. This can be useful to inspect for certain headers.
No longer have to do .json()
on responses to get the data since that’s the default now. If you expect something else than JSON as the response, you can specify the expected response type using an object with the responseType
key.
import { HttpClient, HttpParams } from '@angular/common/http';
.
.
.
constructor(private http:HttpClient){
}
doGET(){
let url = `${this.apiRoot}/get`;
this.http.get(url, {observe: 'response', responseType: 'json'}).subscribe((res)=>{
console.log(res.headers.get('X-Powered-By'));
console.log(res.body);
});
}
doDELETE(){
let url = `${this.apiRoot}/delete`;
this.http.delete(url).subscribe(res => console.log(res));
}
doPOST(){
let url = `${this.apiRoot}/post`;
this.http.post(url, {moo:"foo", goo:"loo"}).subscribe(res => console.log(res));
}
doPUT(){
let url = `${this.apiRoot}/put`;
this.http.put(url, {moo:"foo", goo:"loo"}).subscribe(res => console.log(res));
}
doGETWithQueryParam(){
let url = `${this.apiRoot}/get`;
let search = new HttpParams();
search.set('foo', 'moo');
search.set('limit', 25);
this.http.get(url, {search}).subscribe(res => console.log(res));
}
By default, these requests return observables which we can subscribe to.
We can also convert these observables into promises and handle the asynchronous responses that way.
We just call toPromise()
on the observable that gets returned and this will convert it into a promise.
The toPromise()
function is an observable operator, so we need to import it before use.
import 'rxjs/add/operator/toPromise';
.
.
.
doGETAsPromise(){
let url = `${this.apiRoot}/get`;
this.http
.get(url)
.toPromise()
.then(
(res)=>{ console.log(res); }
);
}
If we can import all RxJS operators, but the below will unnecessarily bloat the application so is not recommended for production.
import 'rxjs/RX';
Like we handle the response as an Observable or as a Promise, we can add error handling function as the second parameter.
In Promise, an error handler is the second argument to then
.
In Observables, an error handler is the second argument to subscribe
.
The err
parameter to the callback is of type HttpErrorResponse
, and contains useful information on what went wrong.
doGETAsPromiseError(){
let url = `${this.apiRoot}/get`;
this.http
.get(url)
.toPromise()
.then(
(res)=>{ console.log(res); },
(err:HttpErrorResponse)=>{
if(err.error instanceof Error){
console.log('An error occurred:', err.error.message);
}else{
console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
}
}
);
}
HTTP headers are bits of meta-data which the browser attaches to your HTTP requests when it sends them to the server. Things like your IP address, the type of browser you are using and so on are added to your headers.
The headers option is often useful to add a few custom headers to your request. It happens to be necessary for some authentication techniques like JSON Web Token.
To send custom headers with our requests, we have to follow steps:
- First need to import helper classes from the HTTP module.
- Create an instance of headers and add our specific header to the mix. The HttpHeaders class is immutable, so every set() returns a new instance and applies the changes.
- Finally we pass the options to the appropriate http function.
import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
.
.
.
doGETWithHeaders(){
// Create options
let opts = {
headers: new HttpHeaders().set('Authorization', 'my-auth-token')
};
// request
let url = `${this.apiRoot}/get`;
this.http
.get(url, opts)
.subscribe(
(res)=>{ console.log(res); },
(err:HttpErrorResponse)=>{ console.error(`Error: ${err.status} ${err.statusText}`); }
);
}
Interceptors provide a mechanism to intercept and/or mutate outgoing requests or incoming responses. They are very similar to the concept of middleware with a framework like Express, except for the frontend.
When your application makes a request, interceptors transform it before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.
The new HTTP client resides in the @angular/common/http
package. You need to register the HttpClientModule
and register the interceptor on the HTTP_INTERCEPTORS
.
The multi: true
option is required and tells Angular that HTTP_INTERCEPTORS
is an array of values, rather than a single value.
The interceptors will be called in the order in which they were provided. So with the above, MyInterceptor would handle http requests first.
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
// your interceptor files
import { MyInterceptor } from './my.interceptor';
import { MyHttpLogInterceptor } from './http.interceptor';
@NgModule({
imports: [ ..., HttpClientModule ],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: MyHttpLogInterceptor, multi: true }
],
...
})
export class AppModule {}
An HTTP interceptor is just an Angular service implementing a specific interface, the HttpInterceptor
.
The class should define an intercept
method to correctly implement HttpInterceptor
.
The intercept method takes two arguments, req
and next
, and returns an observable of type HttpEvent
.
-
req
is the request object itself and is of typeHttpRequest
. -
next
is anHttpHandler
. The handler has ahandle
method that returns our desiredHttpEvent
observable. Most of the time, though, interceptors will make some minor change to the request and forward it to the rest of the chain. That's where thenext
parameter comes in.
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
@Injectable()
export class MyInterceptor implements HttpInterceptor {
intercept(req:HttpRequest<any>, next:HttpHandler):Observable<HttpEvent<any>>{
return next
.handle(request)
.do(evt=>{
if(evt instanceof HttpResponse){
console.log('---> status:', evt.status);
console.log('---> filter:', req.params.get('filter'));
}
});
}
}
HttpRequest
objects are immutable, so in order to modify them, we need to first make a copy, then modify the copy and call handle on the modified copy.
To intercept the response coming back from the server, we can simply hook on the do(..)
operator as the new HTTP module heavily relies on the Observable API.
import { HttpInterceptor, HttpRequest, HttpResponse, HttpErrorResponse, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
@Injectable()
export class MyInterceptor implements HttpInterceptor {
intercept(req:HttpRequest<any>, next:HttpHandler):Observable<HttpEvent<any>>{
// add a custom header
const customReq = req.clone({
headers: req.headers.set('app-language', 'it')
});
// pass on the modified request object
return next
.handle(customReq)
.do((evt:HttpEvent<any>)=>{
if(evt instanceof HttpResponse){
console.log('processing response', evt);
}
})
.catch((response)=>{
if (response instanceof HttpErrorResponse) {
console.log('Processing http error', response);
}
return Observable.throw(response);
});
}
}
A great new feature with HttpClient
is the ability to listen for progress events. The different information will be available during the lifecycle of the request event.
import { Injectable } from '@angular/core';
import {HttpClient, HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpEventType} from from '@angular/common/http';
@Injectable()
export class DataService{
constructor(private http:HttpClient){
}
getData(){
let url = '/some/api';
const req = new HttpRequest('GET', url, {reportProgress: true});
this.http
.request(req)
.subscribe((event:HttpEvent<any>)=>{
switch(event.type){
case HttpEventType.Sent:
console.log('Request sent!');
break;
case HttpEventType.ResponseHeader:
console.log('Response header received!');
break;
case HttpEventType.DownloadProgress:
const kbLoaded = Math.round(event.loaded / 1024);
console.log(`Download in progress! ${ kbLoaded }Kb loaded`);
break;
case HttpEventType.Response:
console.log('Done!', event.body);
break;
}
});
}
}