Contents hide 1) The Http Service 1.1) The Back-End API 1.1.1) GET /api/food 1.1.2) GET /api/books 1.1.3) GET /api/movies 1.1.4) POST /api/food 1.1.5) PUT /api/food/{food_id} 1.1.6) DELETE /api/food/{food_id} 2) Getting Started with HttpClient 2.1) Building the Angular Component 3) Executing a Single HTTP Request 3.1) Executing multiple concurrent HTTP requests 3.2) Writing data to the API 3.2.1) Possible bug in Firefox 3.3) Creating and Saving Data from Our Component In this tutorial, we will see how to make API calls with the HttpClient service In Angular 5. As of Angular 5.0, the older Http service still works, but it’s deprecated and will be removed in a future release. The code samples in this post are compatible with Anguar 4.3, 5.x, and higher. If your project is still using Angular 4.2 or lower, including Angular 2 The Http Service The HttpClient service in Angular 4.3+ is the successor to Angular 2’s Http service and the $http service from AngularJS 1.x. Instead of returning a Promise, its http.get() method returns an Observable object. The Back-End API GET /api/food Returns an array of all existing Food objects in JSON format. GET /api/books Returns an array of all existing Book objects in JSON format. GET /api/movies Returns an array of all existing Movies objects in JSON format. POST /api/food Creates a new Food object in the back-end data store. Accepts a JSON object in the request body. If successful returns a 200 OK response, containing a JSON object representing the data as saved on the server, including the auto-numbered ID. PUT /api/food/{food_id} Updates an existing Food object. Accepts a JSON object in the request body. If successful, returns a 200 OK response, containing a JSON object representing the data as saved on the server. DELETE /api/food/{food_id} Deletes an existing Food object. Does not require a response body. If successful, returns a 200 OK response, containing a JSON object representing the Food object as it existed before it was deleted. The entire Express API code is as follows. const express = require('express'); const bodyParser = require('body-parser'); const path = require('path'); const app = express(); app.use(express.static(__dirname)); app.use(bodyParser.json()); // support json encoded bodies // some data for the API var foods = [ { "id": 1, "name": "Donuts" }, { "id": 2, "name": "Pizza" }, { "id": 3, "name": "Tacos" } ]; var books = [ { "title": "Hitchhiker's Guide to the Galaxy" }, { "title": "The Fellowship of the Ring" }, { "title": "Moby Dick" } ]; var movies = [ { "title": "Ghostbusters" }, { "title": "Star Wars" }, { "title": "Batman Begins" } ]; // the "index" route, which serves the Angular app app.get('/', function (req, res) { res.sendFile(path.join(__dirname,'/dist/index.html')) }); // the GET "books" API endpoint app.get('/api/books', function (req, res) { res.send(books); }); // the GET "movies" API endpoint app.get('/api/movies', function (req, res) { res.send(movies); }); // the GET "foods" API endpoint app.get('/api/food', function (req, res) { res.send(foods); }); // POST endpoint for creating a new food app.post('/api/food', function (req, res) { // calculate the next ID let id = 1; if (foods.length > 0) { let maximum = Math.max.apply(Math, foods.map(function (f) { return f.id; })); id = maximum + 1; } let new_food = {"id": id, "name": req.body.name}; foods.push(new_food); res.send(new_food); }); // PUT endpoint for editing food app.put('/api/food/:id', function (req, res) { let id = req.params.id; let f = foods.find(x => x.id == id); f.name = req.body.name; res.send(f); }); // DELETE endpoint for deleting food app.delete('/api/food/:id', function (req, res) { let id = req.params.id; let f = foods.find(x => x.id == id); foods = foods.filter(x => x.id != id); res.send(f); }); // HTTP listener app.listen(3000, function () { console.log('Example listening on port 3000!'); }); module.exports = app; Getting Started with HttpClient To use the Angular HttpClient, we need to inject it into our app’s dependencies: import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; // replaces previous Http service import { FormsModule } from '@angular/forms'; import { DemoService } from './demo.service'; // our custom service, see below import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule, FormsModule, HttpClientModule], declarations: [AppComponent], providers: [DemoService], schemas: [CUSTOM_ELEMENTS_SCHEMA], bootstrap: [AppComponent] }) export class AppModule { } Building the Angular Component Our demo app contains only one simple component, which contains a few elements to display some simple data. We will be loading data from a few JSON files, to simulate an API call. src/app/app.component.ts import {Component} from '@angular/core'; import {DemoService} from './demo.service'; import {Observable} from 'rxjs/Rx'; @Component({ selector: 'demo-app', template:` <h1>Angular 5 HttpClient Demo App</h1> <h2>Foods</h2> <ul> <li *ngFor="let food of foods">{{food.name}}</li> </ul> ` }) export class AppComponent { public foods; constructor(private _demoService: DemoService) { } } Executing a Single HTTP Request We can use HttpClient to request a single resource, by using http.get. This is very similar to the Angular 2 Http service. Notice that we no longer have to `.map((res:Response) => res.json()` because HttpClient handles this for us src/app/demo.service.ts import {Injectable} from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import {Observable} from 'rxjs/Observable'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable() export class DemoService { constructor(private http:HttpClient) {} // Uses http.get() to load data from a single API endpoint getFoods() { return this.http.get('/api/food'); } } Our Demo service makes the HTTP request and returns the Observable object. To actually get the data from the service, we need to update our component to subscribe to the Observable src/app/app.component.ts ... ngOnInit() { this.getFoods(); } //+ getFoods() { //+ this._demoService.getFoods().subscribe( //+ data => { this.foods = data}, //+ err => console.error(err), //+ () => console.log('done loading foods') //+ ); //+ } The subscribe() method takes three arguments which are event handlers. They are called onNext, onError, and onCompleted. The onNext method will receive the HTTP response data. Observables support streams of data and can call this event handler multiple times. In the case of the HTTP request, however, the Observable will usually emit the whole data set in one call. The onError event handler is called if the HTTP request returns an error code such as a 404. The onCompleted event handler executes after the Observable has finished returning all its data. This is less useful in the case of the Http.get() call, because all the data we need is passed to the onNext handler. Executing multiple concurrent HTTP requests Many times, we need to load data from more than one source, and we need to delay the post-loading logic until all the data has loaded. ReactiveX Observables provide a method called forkJoin() to wrap multiple Observables. Its subscribe() method sets the handlers on the entire set of Observables. To run the concurrent HTTP requests, let’s add the following code to our service src/app/demo.service.ts ... @Injectable() export class DemoService { constructor(private http:HttpClient) {} // Uses http.get() to load data from a single API endpoint getFoods() { return this.http.get('/api/food'); } //+ Uses Observable.forkJoin() to run multiple concurrent http.get() requests. //+ // The entire operation will result in an error state if any single request fails. //+ getBooksAndMovies() { //+ return Observable.forkJoin( //+ this.http.get('/api/books'), //+ this.http.get('/api/movies') //+ ); //+ } } Notice that forkJoin() takes multiple arguments of type Observable. These can be Http.get() calls or any other asynchronous operation which implements the Observable pattern. We don’t subscribe to each of these Observables individually. Instead, we subscribe to the “container” Observable object created by forkJoin(). When using Http.get() and Observable.forkJoin() together, the onNext handler will execute only once, and only after all HTTP requests complete successfully. It will receive an array containing the combined response data from all requests. In this case, our books data will be stored in data[0] and our movies data will be stored in data[1]. The onError handler here will run if either of the HTTP requests returns an error code Next, we subscribe to the new method in our component: src/app/app.component.ts import {Component} from '@angular/core'; import {DemoService} from './demo.service'; import {Observable} from 'rxjs/Rx'; @Component({ selector: 'demo-app', template:` <h1>Angular 5 HttpClient Demo App</h1> <p>This is a complete mini-CRUD application using an Express back-end. See src/app/demo.service.ts for the API call code.</p> <h2>Foods</h2> <ul> <li *ngFor="let food of foods">{{food.name}}</li> </ul> //+ <h2>Books and Movies</h2> //+ <p>This is an example of loading data from multiple endpoints using Observable.forkJoin(). The API calls here are read-only.</p> //+ <h3>Books</h3> //+ <ul> //+ <li *ngFor="let book of books">{{book.title}}</li> //+ </ul> //+ <h3>Movies</h3> //+ <ul> //+ <li *ngFor="let movie of movies">{{movie.title}}</li> //+ </ul> ` }) export class AppComponent { public foods; //+ public books; //+ public movies; ... getFoods() { ... } //+ getBooksAndMovies() { //+ this._demoService.getBooksAndMovies().subscribe( //+ data => { //+ this.books = data[0] //+ this.movies = data[1] //+ } //+ // No error or completion callbacks here. They are optional, but //+ // you will get console errors if the Observable is in an error state. //+ ); //+ } } Writing data to the API To write data to our API, we need to add several new methods to our DemoService class: src/app/demo.service.ts ... @Injectable() export class DemoService { constructor(private http:HttpClient) {} ... //+ createFood(food) { //+ let body = JSON.stringify(food); //+ return this.http.post('/api/food/', body, httpOptions); //+ } //+ updateFood(food) { //+ let body = JSON.stringify(food); //+ return this.http.put('/api/food/' + food.id, body, httpOptions); //+ } //+ deleteFood(food) { //+ return this.http.delete('/api/food/' + food.id); //+ } } Notice that our createFood(), updateFood(), and deleteFood() methods use API endpoints which return the saved object in JSON format. Returning the object when creating, updating, or deleting is a nice convenience for the developer of the front-end application. Not all APIs return this data. Some may return a different status code, some XML data, or nothing at all. Consult the documentation for your API to determine what the response format will look like. Possible bug in Firefox At the time of this writing, the API calls on one of my projects were failing when to run in Firefox. It seems that Angular 2 was not sending the Content-type: application/json headers with the requests. If your API supports this, you might be able to work around the problem by changing your API URLs to include the .json extension (e.g., /api/food/1.json). Creating and Saving Data from Our Component Now that we have the service in place, we can add some basic CRUD features to our AppComponent. src/app/app.component.ts import {Component} from '@angular/core'; import {DemoService} from './demo.service'; import {Observable} from 'rxjs/Rx'; @Component({ selector: 'demo-app', template:` <h1>Angular 5 HttpClient Demo App</h1> <p>This is a complete mini-CRUD application using an Express back-end. See src/app/demo.service.ts for the API call code.</p> <h2>Foods</h2> <ul> //- <li *ngFor="let food of foods">{{food.name}}</li> //+ <li *ngFor="let food of foods"><input type="text" name="food-name" [(ngModel)]="food.name"> //+ <button (click)="updateFood(food)">Save</button> //+ <button (click)="deleteFood(food)">Delete</button> //+ </li> </ul> //+ <p>Create a new food: <input type="text" name="food_name" [(ngModel)]="food_name"><button (click)="createFood(food_name)">Save</button></p> <h2>Books and Movies</h2> ... ` }) export class AppComponent { public foods; public books; public movies; + public food_name; ... getFoods() { ... } getBooksAndMovies() { ... } //+ createFood(name) { //+ let food = {name: name}; //+ this._demoService.createFood(food).subscribe( //+ data => { //+ // refresh the list //+ this.getFoods(); //+ return true; //+ }, //+ error => { //+ console.error("Error saving food!"); //+ return Observable.throw(error); //+ } //+ ); //+ } //+ updateFood(food) { //+ this._demoService.updateFood(food).subscribe( //+ data => { //+ // refresh the list //+ this.getFoods(); //+ return true; //+ }, //o+ error => { //+ console.error("Error saving food!"); //+ return Observable.throw(error); //+ } //+ ); //+ } //+ deleteFood(food) { //+ if (confirm("Are you sure you want to delete " + food.name + "?")) { //+ this._demoService.deleteFood(food).subscribe( //+ data => { //+ // refresh the list //+ this.getFoods(); //+ return true; //+ }, //+ error => { //+ console.error("Error deleting food!"); //+ return Observable.throw(error); //+ } //+ ); //+ } //+ } } You’ll notice that we added some basic form fields and buttons to the template, and new methods createFood(), updateFood(), and deleteFood() to the component class. These are called when users click the buttons in the template, and handle saving and deleting the data. For simplicity, I have used a simple JavaScript confirm() dialog as a delete confirmation. An enhancement might be to implement a nicer-looking dialog using another Angular component. Get Some More Angular 2+ Articles Here! Share this:TwitterFacebookRedditLinkedInWhatsAppPrintTumblr Related Tags: Angular CLI, Angular5, HttpRequest, typescript