Using Angular v4's New HTTP Client

Caleb Oki
πŸ‘οΈ 3,844 views
πŸ’¬ comments

With the release of Angular v4.3 in July, some new features where introduced. Among them was a new HttpClientModule, @angular/common/http replacing @angular/http. It is smaller, easier and a more powerful library for making HTTP requests.

A new HttpClient service was also included in the HttpClientModule and it can be used to initiate HTTP request. In this tutorial I would show you how to implement this new client and examine some of its features.

We've talked about the HTTP Client in depth in a previous article: Angular 2 HTTP Requests with Observables

TLDR

Here's the changes you'll need to make from the old (pre-v4) to the new (v4+)

For importing into an NgModule:

// below v4 ==========================================
import { HttpModule } from '@angular/http';

...
@NgModule({
    imports: [
        HttpModule
    ]
})
...

// v4+ ===============================================
import { HttpClientModule } from '@angular/common/http';

...
@NgModule({
    imports: [
        HttpClientModule
    ]
})
...

And for using inside of a service:

// below v4 ==========================================
import { Http } from '@angular/http';

...
constructor(private http: Http) {}
...

// v4+ ===============================================
import { HttpClient } from '@angular/common/http';

...
constructor(private http: HttpClient) {}
...

With that quick look out of the way, let's take a deeper dive.

Installing Angular v4

To get started install the Angular CLI using Node and npm if you do not have it installed already.

npm install -g @angular/cli@latest

The -g switch for installing it globally and @latest for the latest version. After the installation is complete run the following command to scarfold a new application

ng new httptutorial

This downloads the project's template and installs all dependences. The project directory structure should look like this

// end-to-end-tests
|- e2e/
  |----- app.e2e-spec.ts
  |----- app.po.ts
  |----- tsconfig.e2e.json

// npm dependencies
|- node_modules/

// public facing app. built things go here. this wont show until we run a build
|- dist/

// where most of the work will be done
|- src/
  |----- app/
      |----- app.component.css|html|spec.ts|ts
      |----- app.module.ts
  |----- assets/
  |----- environments/
      |----- environment.prod.ts|ts
  |----- favicon.ico
  |----- index.html
  |----- main.ts
  |----- polyfills.ts
  |----- styles.css
  |----- test.ts
  |----- tsconfig.app.json
  |----- tsconfig.spec.json
  |----- typings.d.ts

// overall configuration
|- .angular-cli.json  // the main configuration file
|- .editorconfig      // editorconfig which is used in some VS Code setups
|- .gitignore
|- karma.conf.js
|- package.json
|- protractor.conf.js
|- README.md
|- tsconfig.json
|- tslint.json

Open the package.json file and update the angular dependences to version 4.3.6. So the dependencies and devDependenciessection of the file should look like this:

"dependencies": {
    "@angular/animations": "^4.3.6",
    "@angular/common": "^4.3.6",
    "@angular/compiler": "^4.3.6",
    "@angular/core": "^4.3.6",
    "@angular/forms": "^4.3.6",
    "@angular/http": "^4.3.6",
    "@angular/platform-browser": "^4.3.6",
    "@angular/platform-browser-dynamic": "^4.3.6",
    "@angular/router": "^4.3.6",
    "core-js": "^2.4.1",
    "rxjs": "^5.4.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "1.3.2",
    "@angular/compiler-cli": "^4.3.6",
    "@angular/language-service": "^4.3.6",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.1.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.3.2",
    "typescript": "~2.3.3"
  }

Then within the project directory run

npm install

This would pull in the dependences in the package.json file. To see if everything works correctly, start the development web server running:

ng serve

This starts the development web server at http://localhost:4200. Visit this url, and you should see something like this:

Installing the HTTP module

Next import the HttpClientModule in the application's root module that is the src/app/app.module.ts file and add it in the imports property. So the file should look like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now for the HttpClient to be used in your components, it needs to be injected into the class constructor. In src/app/app.component.ts import the HttpClient then inject it into the constructor as shown below:

import { Component } from '@angular/core';

import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';

  constructor( private http: HttpClient ) { //dependency injection, creating an instance of HttpClient called http

  }
}

Now you would be able to make CRUD operations and execute HTTP requests. The HTTP methods available are post, put, delete, patch, head and jsonp.

HTTP GET

To demostrate the get method, we would be quering a fake REST API. Head over to http://jsonplaceholder.typicode.com/ and scroll down to Resources. You should see a page like this: Then under Resources click on /posts Note that we see a bunch of json objects here, each of which has four properties: userId, id, title and body. So when we hit the url: http://jsonplaceholder.typicode.com/posts from our Angular app, we would get the above result. The other HTTP methods also work as expected.

Now edit the src/app/app.component.ts to look like this:

import { Component, OnInit } from '@angular/core'; // importing the OnInit interface

import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { // implementing OnInit
  title = 'app';

  constructor( private http: HttpClient ) {

  }
  ngOnInit(): void { // adding the lifecycle hook ngOnInit
    this.http.get('http://jsonplaceholder.typicode.com/posts').subscribe(data => {
      console.log(data); // using the HttpClient instance, http to call the API then subscribe to the data and display to console
    });
  }

}

Here we are calling the API endpoint in the ngOnInit lifecycle hook. This hook is invoked when our component has been initialized. First we import the OnInit interface, then implement this interface in the class definition. Then we call the ngOnInit method, inside which we call the HttpClient instance http, which we created earlier in the constructor.

We call the get method from this instance which expects the URL of the API endpoint we are interested in. The get method returns an observable so we need to subscribe to this observable so as to be informed when the response arrives, this is done by calling the subscribe method. In the subscribe method we define an event handler which gets the data, which we can then print into the console. The output which is displayed in the browser console should look like the following:

These are the json objects pulled from the url: http://jsonplaceholder.typicode.com/posts.

To access each one of the properties of the response object, for example the data.userId property, we need to cast the response object to a type which is containing the corresponding properties. To do this, let's define an interface. Enter the following in the src/app/app.component.ts file just after the import statements:

interface DataResponse {
  userId: string;
  id: string;
  title: string;
}

Next, edit the get call to make use of the DataResponse interface:

this.http.get<DataResponse>('http://jsonplaceholder.typicode.com/posts/1').subscribe(data => {
      console.log('UserId: ' + data.userId);
      console.log('Id: ' + data.id);
      console.log('Title: ' + data.title);
      console.log('Body: ' + data.body);
    });

Now we can access the properties userId, title and body individually. The output at the console should look like this:

We might want to display an error message when the http request fails. To do this, first import HttpErrorResponse from @angular/common/http then create a error handler function by adding a callback to the subscribe method, then define a parameter of type HttpErrorResponse for the error handler function:

import { HttpClient, HttpErrorResponse } from '@angular/common/http';

// ...

this.http.get<DataResponse>('https://jsonplaceholder.typicode.com/posts/1').subscribe(data => {
      console.log('UserId: ' + data.userId);
      console.log('Id: ' + data.id);
      console.log('Title: ' + data.title);
      console.log('Body: ' + data.body);
    },
    (err: HttpErrorResponse) => {
      if (err.error instanceof Error) {
        console.log('Client-side error occured.');
      } else {
        console.log('Server-side error occured.');
      }
    }
  );

HTTP POST

Just as before, we would use the JSONPlaceholder service to demostrate HTTP POST. Note however that as this is a fake service, data is not persistent but the API responds as if a real API is called. The endpoint of the POST request is http://jsonplaceholder.typicode.com/posts. If you visit this url you will see that we have four properties available to us: userId, id, title and body. To use this endpoint to create a new record, add a second call inside the ngOnInit lifecycle:

this.http.post('http://jsonplaceholder.typicode.com/posts', {
      title: 'foo',
      body: 'bar',
      userId: 1
    })
      .subscribe(
        res => {
          console.log(res);
        },
        err => {
          console.log('Error occured');
        }
      );

The post method is again returning an observable so we need to subscribe to this observable as before, this is done by calling the subscribe method. In the subscribe method we define an event handler which gets the data, which we can then print into the console. Then we add the error handler to print of information if an error occured. The output displayed in the browser console should look like the following:

HTTP Interceptors

Another new feature of the HTTP Client module are interceptors. An interceptor sits between the application and a backend API. With interceptors we can manipulate a request coming from our application before it is actually submitted and sent to the backend. The converse is also true, that is a response arriving from the backend can be altered before it is submitted and processed by our application. To demostrate this, we would intercept the header information coming from the get request to http://jsonplaceholder.typicode.com/posts/1. The header field we would be altering is the Accept-Language field coming from the API. Create a new file src/app/typicode.interceptor.ts and enter the following code:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/observable';
@Injectable()
export class TypicodeInterceptor implements HttpInterceptor {
  intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authReq = req.clone({
      headers: req.headers.set('Accept-Language', 'Test')
    });
    return next.handle(authReq);
  }
}

First we are importing injectable from @angular/core then we import HttpEvent, HttpInterceptor, HttpHandler from @angular/common/http. Finally the Observable package is imported from rxjs/observable.

Next we add the @injectable decorator, then create the TypicodeInterceptor class which implements the HttpInterceptor interface. We then add the interceptor method within the class implementation.

This method takes a request, alters it, before passing it on for further processing to our application. Hence we are passing two parameters into this method; the request itself of type HttpRequest<any> and a parameter called next which is of type HttpHandler. The method returns an observable of type HttpEvent<any>

Next we call the req.clone() method to clone the original HTTP request. Inside this method we alter the header field which is done by calling the req.headers.set() method. Here we altering the Accept-Language field changing it's value to Test.

Finally the newly created request object (with the header included) is passed on for further processing by using the next.handle method.

Interceptor Provider

To get the interceptor to work for our application, we need to edit src/app/app.module.ts file.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { TypicodeInterceptor } from './typicode.interceptor';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: TypicodeInterceptor,
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }

Here we have imported HTTP_INTERCEPTORS from @angular/common/http and the TypicodeInterceptor class we created earlier in src/app/typicode.interceptor.ts. Next we’re inserting a new object to the array which is assigned to the providers property of @NgModule. This object contains three properties:

  • provide: needs to be set to HTTP_INTERCEPTORS in order to specify that a HTTP interceptor is provided
  • useClass: needs to be set to our interceptor class type
  • multi: needs to be set to multi to tell Angular that HTTP_INTERCEPTORS is an array of values, rather than a single value

To see this interception in action click on the network tab, reload the page then select the HTTP request on the left panel, and the HTTP headers will be displayed on the right panel. The request we are interested in is the get request which calls the url: http://jsonplaceholder.typicode.com/posts/1.

Conclusion

The new HTTP client module makes it easier to deal with HTTP backend interfaces like REST API's. To recap we have covered the basic setup of the HttpClientModule, demostrated how to use the get and post method and shown how to use the new interceptor feature. You can think of a few use cases were these features would be required. Also take a look at this project on Github. If you have any questions or comments do not hesitate to post them below.

Caleb Oki

3 posts

Laravel and Vue.js web developer