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
Table of Contents
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 devDependencies
section 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.