Simply about new HttpClient in Angular

Last Updated On

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:

  1. Get JSON string (which is of type Response).
  2. Convert it to an object using JSON.parse() (deserialization process)
  3. 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 with HttpClientModule. New HttpClientModule should be imported from @angular/common/http.
  • In your actual service replace Http with HttpClient (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 exaple:

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.