Table of contents
Introduction
The new HttpClient
was introduced in Angular since
version 4.3. However, starting from 5th release “old” Http
was marked as depreciated and that’s one of the reasons why I want to
bring your attention to the newer version.
New features
Let’s explore what’s new in the updated HttpClient
.
Automatic conversion to from JSON to an object
When we receive data from the internet we use JSON format. To be able to process this data in our code we need to create an object based on JSON data that we got. The same logic applies backward: before sending a data each object should be converted to a JSON string. This process is also known as deserialization(converting from string to an object) and serialization(converting from object to a string).
Let’s see a basic example of getting data using old Angular http:
getData() {
this.http.get('https://example.com/somejson')
.map((response: Response) => {
const data = JSON.parse(response._body);
// or new Fetch API way
// const data = response.json();
console.log(response);
})
}
There are three things happening here, so let’s put them in order:
- Get JSON string (which is of type
Response
). - Convert it to an object using
JSON.parse()
(deserialization process) - Log the result into the console.
With a new HttpClient
this conversion happens behind the
scenes:
getData() {
this.http.get('https://example.com/somejson')
.map((data) => {
console.log(data);
})
}
However, if you want to preserve old behavior you still can do that
very easily by passing extra object {observe:'response'}
as
a parameter into http.get()
method (more about it here).
Response type definition
Taking into account that all conversion happens behind the scenes we
can truly say that HttpClient
returns an object. So, why
wouldn’t we take this opportunity and define expected data type, like
this:
getData() {
this.http.get<User>('https://example.com/somejson')
.map((data:User) => {
console.log(data);
})
}
In this example, I explicitly set expected data type to be
User
.
Event firing
Whenever you send or receive a request, you have the ability to fire
an event. You can track event type and use your logic to react to it.
Let’s jump straight into an example with one extra step: In order to use
this feature, we need to pass an extra parameter into the
http.get()
method:
getData() {
this.http.get('https://example.com/somejson', {
observe: 'events'
})
.map((data:HttpEvent<object>) => {
console.log(data);
})
}
The extra parameter I mentioned is {'observe': events }
object. In addition to that, our data isn’t of type User
anymore. It’s of type HttpEvent<object>
instead.
HttpEvent<object>
actually contains types of events
you can react to: Sent
, UploadProgress
,
ResponseHeader
, DownloadProgress
,
Response
, User
.
Alright, now we know event types. Let’s modify our code block and
react to a Sent
event:
getData() {
this.http.get('https://example.com/somejson', {
observe: 'events'
})
.map((data:HttpEvent<object>) => {
if(data.type === HttpEventType.Sent) {
console.log('Sent Event has been fired!', data);
}
})
}
In the example above we checked whether our event is type of
Sent
and console logged it. You can use similar logic to
trigger your own function.
Simplified syntax for headers
In order to set a custom header on the request, we need to
instantiate HttpHeaders()
object and pass
('header', 'value')
into a function. Here’s an example:
const headers = new HttpHeaders().set('Content-Type', 'text');
In this example we set Content-Type
header value to be
text
(application/json
is the default one). We
can also append headers by chaining
HttpHeaders()
constructor:
const headers = new HttpHeaders().set('Content-Type', 'text').append('Authorization', 'CustomToken12345');
Final request with custom headers will look like this:
getData() {
const requestHeaders = new HttpHeaders().set('Content-Type', 'text').append('Authorization', 'CustomToken12345');
this.http.get('https://example.com/somejson', {
headers: requestHeaders
})
.map((data:HttpEvent<object>) => {
console.log(data);
})
}
Interceptors
This is an advanced feature that allows us to (as a name applies)
intercept each request/response and modify it before sending/receiving.
If the term sounds scary, you can simply think about it as a middle
layer between Angular application and a backend. In order to use it, we
need to import it from @angular/common/http
at a module
level. Here’s how you do it:
app.module.ts
import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';
@NgModule({
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: NoopInterceptor,
multi: true,
}],
})
export class AppModule {}
Since interceptor module was imported into our app, we can now use it
by implementing our own interceptor service. This service will “catch”
each request and append an Authorization
header.
interceptor.service.ts
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class AppInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const modifiedRequest = req.clone();
modifiedRequest.headers.append('Authorization', 'myCustomToken123123')
return next.handle(modifiedRequest);
}
}
Following this logic, Authorization token will be appended to each
request. It’s also possible to override request’s headers by using
set()
method.
Please note: By default all requests are immutable or in
other words, you can’t modify them. In order to be able to do that, an
exact copy of the request should be created. That’s exactly what has
been done in the example above using a clone()
method. As a
result modifiedRequest
variable contains the same data as
an original request.
How to migrate to a new HttpClient from the “old” one:
- The first step is to make sure that proper Angular version has been installed, i.e. 4.3 and higher.
- In the project’s module replace
HttpModule
withHttpClientModule
. NewHttpClientModule
should be imported from@angular/common/http
. - In your actual service replace
Http
withHttpClient
(the same rule –HttpClient
should be imported from@angular/common/http
as well) - Optional: if you set custom headers, they should be rewritten as well. Please see example below.
Here’s “old” to “new” configuration comparison:
app.module.ts
// Old
import { HttpModule } from '@angular/http';
...
@NgModule({
...
imports: [
HttpModule,
],
...
})
// New
import { HttpClientModule } from '@angular/common/http';
...
@NgModule({
...
imports: [
HttpClientModule,
],
...
})
example.service.ts
// Old
import { Http, Headers } from '@angular/http';
...
@Injectable()
export class ExampleService {
private _headers = new Headers({'Authentication': 'application/json'});
constructor(private http: Http) { }
public getData() {
return this.http.get('http://example.com', this._headers)
.map((rawResponse: any) => {
return JSON.parse(rawResponse._body);
});
}
}
// New
import { HttpClient, HttpHeaders } from '@angular/common/http';
...
@Injectable()
export class ExampleService {
private _headers = {headers: new HttpHeaders().set('Authentication', 'application/json')};
constructor(private http: HttpClient) { }
public getData() {
return this.http.get('http://example.com', this._headers);
}
}
Live example
Here, I’m attaching live example to play with:
Conclusion
Basically, there weren’t big changes to already existing syntax.
HttpClient
preserved the same method names from the old
http: http.get()
, http.post()
etc.
This was a basic overview of the Angular new HttpClient. If you want to know how to write unit tests for HttpClient please take a look at this article or dive deeper at the official documentation.