In this Angular tutorial, we’ll learn how to use the HttpClient module in Angular application to make RESTFull API Ajax calls.
We’ll set up a new Angular 12 project and create API endpoints to perform Create, Read, Update and Delete operations using HTTP methods using a mock server using the json-server
package. Using json-server
we can create a local JSON file which can act as a database for our application. There is no difference between a local mock server and a real database.
We’ll perform HTTP operation using API endpoints to add, edit, delete and get list of items listed in an Angular Material Datatable. This Material Datatable will have action column using which user an Edit or Delete a row. There will be a form over the table to Add or Update existing rows in the table.
After implementation, our application will look like this
First, let’s have a look at Angular HttpClient and its features.
What is Angular HttpClient?
A reactive application like Angular, communicate with server databases to fetch data in form of JSON object using Ajax API calls.
These API Ajex calls use XMLHttpRequest service for old browsers or fetch() methods under the hood.
The HttpClient service provided by Angular’s @angular/common/http
package provides a simple interface to make HTTP calls with many optimized and efficient browser support features.
Moreover, we can also use RxJS based Observables and operators to handle client-side or server-side errors.
Using Interceptors service we can modify requests and responses of API calls and even cancels them.
Let’s get started!
# Setup Angular CLI
Angular CLI is the most prefered and official way for creating a new Angular project.
Make sure you have installed the latest version on Angular CLI tool on your system.
Run following npm command to install
$ npm install -g @angular/cli
# Create a new Angular project
Next, create a new Angular project by running following ng command in the terminal
$ ng new angular-httpclient-tutorial
On hitting above command, ng CLI will ask few configurational questions
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
Now move to the project folder
$ cd angular-httpclient-tutorial
Run the project by executing below command
$ ng serve --open
As we have created the Angular project successfully, lets mover further to create a dummy mock JSON server.
# Setup a Mock JSON Server
For testing and learning HttpClient module, we need to test Http methods, which will communicate to the server by calling Rest APIs.
These RESTFull APIs return JSON data which is then consumed by Angular application. This API JSON data may come from any third-party API, Server Database or any local server database.
Here we will create a dummy mock JSON API server using the json-server
package. using this package we can create a mock server using which we can perform all HTTP methods like GET
, POST
, PUT
, PATCH
and DELETE
.
First, install the json-server
package by running bellow npm command in the terminal window:
$ npm install -g json-server
After that create a new folder API in the project root and place JSON file data.json at ~angular-httpclient-tutorial/API/data.json
The data.json file will work as RESTfull server. We will add some dummy students data. So that will act like a database on which we will perform CRUD operations.
# Start JSON Server
To start the JSON server using json-server, run following command in a new terminal:
$ json-server --watch ./API/data.json
Now you can access our mock server at http://localhost:3000/students
Following are the API URLs available on our server:
GET /posts – fetch all students
GET /posts/: id – fetch a single student detail by id
POST /posts – create a new student
PUT /posts/: id – update a student by id
PATCH /posts/: id – partially update a student by id
DELETE /posts/:id – delete a student by id
As we are ready with our server, next we will import HttpClientModule in Angular project to use HTTP services.
# Configure HttpClient in Angular 9 project
Before using HTTP services, we need to import HttpClientModule
from @angular/common/http
class.
Now open the app.module.ts file, then make the following changes:
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
We will add Angular Material Datatable to perform CRUD operations on Students data. So let us install and configure Material UI library in our project.
# Install and Setup Angular Material
Angular Material is a UI library which provides several easy to use feature components. In this tutorial, we will use Material Datatables to show students records and perform the various inline operation on students records.
Run following npm command in terminal to install Material library and answer some configuration answers.
$ ng add @angular/material
? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
? Set up global Angular Material typography styles? Yes
? Set up browser animations for Angular Material? Yes
To use Material UI components, we need to import modules of components which we are going to use in the application’s module so that these will be available in our class components to use.
As we will be using Material Datatables with pagination, so we need to import MatTableModule
and MatPaginatorModule
.
To update and add students rows we will add Material Form as well, for that we will also import FormsModule
, ReactiveFormsModule
, MatInputModule
, and MatButtonModule
as well
in the app.module.ts file as shown below:
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatTableModule } from '@angular/material/table'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { StudentsComponent } from './pages/students/students.component'; @NgModule({ declarations: [ AppComponent, StudentsComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule, BrowserAnimationsModule, FormsModule, ReactiveFormsModule, // Material Modules MatTableModule, MatPaginatorModule, MatInputModule, MatButtonModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
# Creating a Service to Communicate Server through HTTP methods
Now we’ll create a new service to separate all HTTP methods which will communicate to server and do CRUD operations.
Let’s create a new serve HttpDataService under services folder by running following ng command in the terminal window:
$ ng generate service services/http-data
Above generate
command will create the HttpDataService
for us at this location ~src/app/services/http-data.service.ts
Also, create an Interface class for Students data by running following command defining the type of values for student item.
$ ng generate class models/Student
then replace the following content in the newly created file “~/models/student.ts”
export class Student {
id: number;
name: string;
age: string;
address: string;
}
Our service will be going to play an important role in maintaining a connection with the server. Let us dive deep into this file and check what it will have?
Add the server API URL for end-points and define in the base_path
variable. This is the path which opens up on running our json-server
// API path
base_path = 'http://localhost:3000/students';
Note: Make sure your server is still running.
We’ll import these three classes
HttpClient
: This class provides HTTP methods like get()
, post()
, put()
and delete()
.
HttpHeaders
: For setting request headers in HTTP calls we use this class.
// Http Options
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}
HttpErrorResponse
: Used to efficiently handle errors from client-side or server-side.
// Handle API errors
handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
# RxJs functions and operators to the rescue
The RxJs library provides many useful function and operator which we will use in our service:
Observables
: Observables are used to perform asynchronous tasks like HTTP calls. We can subscribe them to get success or error response. They provide several other features file cancellations and continuous event retrieval, unlike promises.
throwError
: This method is used to intentionally throw an error with a message when an HTTP request fails.retry()
: The retry operator is used to make HTTP call again for the number of times specified when a call fails due to network server issues.
catchError()
: This method catches the error and throws to errorHandler# Defining CRUD Methods
json-server
.Create a Student
The new student will be created using the post()
method
// Create a new item
createItem(item): Observable<Student> {
return this.http
.post<Student>(this.base_path, JSON.stringify(item), this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
The createItem()
method is accepting item
attribute with student details to add.
Retrieve Student Details
To fetch single student details we use get()
method with student id
whose detail needs to be checked.
// Get single student data by ID
getItem(id): Observable<Student> {
return this.http
.get<Student>(this.base_path + '/' + id)
.pipe(
retry(2),
catchError(this.handleError)
)
}
Retrieve All Students
Similarly, we will make get
call to fetch all students list
// Get students data
getList(): Observable<Student> {
return this.http
.get<Student>(this.base_path)
.pipe(
retry(2),
catchError(this.handleError)
)
}
Update single student
The put()
method will update single student with id passed
// Update item by id
updateItem(id, item): Observable<Student> {
return this.http
.put<Student>(this.base_path + '/' + id, JSON.stringify(item), this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
Delete a single student
The delete()
HTTP method will delete a single record whose id is passed
// Delete item by id
deleteItem(id) {
return this.http
.delete<Student>(this.base_path + '/' + id, this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
After combining all explained code the final http-data.service.ts file will look like this:
// http-data.servie.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Student } from '../models/student';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class HttpDataService {
// API path
base_path = 'http://localhost:3000/students';
constructor(private http: HttpClient) { }
// Http Options
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}
// Handle API errors
handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
};
// Create a new item
createItem(item): Observable<Student> {
return this.http
.post<Student>(this.base_path, JSON.stringify(item), this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
// Get single student data by ID
getItem(id): Observable<Student> {
return this.http
.get<Student>(this.base_path + '/' + id)
.pipe(
retry(2),
catchError(this.handleError)
)
}
// Get students data
getList(): Observable<Student> {
return this.http
.get<Student>(this.base_path)
.pipe(
retry(2),
catchError(this.handleError)
)
}
// Update item by id
updateItem(id, item): Observable<Student> {
return this.http
.put<Student>(this.base_path + '/' + id, JSON.stringify(item), this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
// Delete item by id
deleteItem(id) {
return this.http
.delete<Student>(this.base_path + '/' + id, this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
)
}
}
# Create new pages
To show students data in a table, we will create a new students component and update the app-routing.module.ts file to open /students
page on application load.
Create the StudentsComponent
 by running below generate command:
$ ng generate component pages/students
Setup the App Routing Module
Now update the app-routing.module.ts file with below code.
// app-routing.module.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { StudentsComponent } from './pages/students/students.component'; const routes: Routes = [ { path: '', redirectTo: '/students', pathMatch: 'full' }, { path: 'students', component: StudentsComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
# Using HttpDataService in Students Page
As we defined our HttpDataService as providedIn: 'root'
so we can directly use it in our Angular application. This will share a single instance across the application.
To use service methods in our student’s page at ~src/app/pages/students/students.component.ts, we need to import
it and then add in component the contractor() method as shown below:
...
import { HttpDataService } from 'src/app/services/http-data.service';
@Component({
selector: 'app-students',
templateUrl: './students.component.html',
styleUrls: ['./students.component.css']
})
export class StudentsComponent implements OnInit {
constructor(private httpDataService: HttpDataService) { }
...
}
# Adding Angular Material Datatable
Next, we will add a Datatable with Students columns and an extra Actions column where we will do inline Edit, Delete and Update operations.
For creating the Material datatable, the mat-table
directive is used. We are also adding pagination by appending the mat-paginator
directive just after ending </table>
tag.
Update the students.component.html file with below code to build a Material datatable:
<div class="table-wrapper">
<!-- Form to edit/add row -->
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef> #Id </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef> Age </th>
<td mat-cell *matCellDef="let element"> {{element.age}} </td>
</ng-container>
<ng-container matColumnDef="address">
<th mat-header-cell *matHeaderCellDef> Address </th>
<td mat-cell *matCellDef="let element"> {{element.address}} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let element">
<a href="javascript:void(0)" (click)="editStudent(element)">Edit</a> |
<a href="javascript:void(0)" (click)="deleteStudent(element.id)">Delete</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
[ngClass]="{'editable-row': studentData.id === row.id}"></tr>
</table>
<mat-paginator [pageSize]="5" [pageSizeOptions]="[5, 10, 15]" showFirstLastButtons></mat-paginator>
</div>
In the last actions
column there are two actions to Edit with editStudent()
method and Delete with deleteStudent()
method for the row.
To add a new row or update the data in the existing row we will add a form above table.
<form (submit)="onSubmit()" #studentForm="ngForm">
<mat-form-field>
<input matInput placeholder="Name" name="name" required [(ngModel)]="studentData.name">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Age" name="age" required [(ngModel)]="studentData.age">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Address" name="address" required [(ngModel)]="studentData.address">
</mat-form-field>
<ng-container *ngIf="isEditMode; else elseTemplate">
<button mat-button color="primary">Update</button>
<a mat-button color="warn" (click)="cancelEdit()">Cancel</a>
</ng-container>
<ng-template #elseTemplate>
<button mat-button color="primary">Add</button>
</ng-template>
</form>
The text in form submit button will change based on the boolean value in the isEditMode
variable.
# Update Component Class
After adding Material datatable and Form, let us update students.component.ts file with required methods.
First, initialize the Template driven form with the NgFormÂ
@ViewChild('studentForm', { static: false })
studentForm: NgForm;
Import the Students
class which we created and define a new variable studentData
of type Students
studentData: Student;
Then define the dataSource
and displayedColumns
with MatPaginator
class to build our Datatable
dataSource = new MatTableDataSource();
displayedColumns: string[] = ['id', 'name', 'age', 'address', 'actions'];
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
Then we will add methods to Add, Delete, Edit, Update and Get Students list in the class file as shown below:
...
getAllStudents() {
this.httpDataService.getList().subscribe((response: any) => {
this.dataSource.data = response;
});
}
editItem(element) {
this.studentData = _.cloneDeep(element);
this.isEditMode = true;
}
cancelEdit() {
this.isEditMode = false;
this.studentForm.resetForm();
}
deleteItem(id) {
this.httpDataService.deleteItem(id).subscribe((response: any) => {
// Approach #1 to update datatable data on local itself without fetching new data from server
this.dataSource.data = this.dataSource.data.filter((o: Student) => {
return o.id !== id ? o : false;
})
console.log(this.dataSource.data);
// Approach #2 to re-call getAllStudents() to fetch updated data
// this.getAllStudents()
});
}
addStudent() {
this.httpDataService.createItem(this.studentData).subscribe((response: any) => {
this.dataSource.data.push({ ...response })
this.dataSource.data = this.dataSource.data.map(o => {
return o;
})
});
}
updateStudent() {
this.httpDataService.updateItem(this.studentData.id, this.studentData).subscribe((response: any) => {
// Approach #1 to update datatable data on local itself without fetching new data from server
this.dataSource.data = this.dataSource.data.map((o: Student) => {
if (o.id === response.id) {
o = response;
}
return o;
})
// Approach #2 to re-call getAllStudents() to fetch updated data
// this.getAllStudents()
this.cancelEdit()
});
}
...
We are calling HttpDataService
methods to communicate with our json-server
After adding the above method the final students.component.ts file will look like this:
// students.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { HttpDataService } from 'src/app/services/http-data.service';
import * as _ from 'lodash';
import { NgForm } from '@angular/forms';
import { Student } from 'src/app/models/student';
@Component({
selector: 'app-students',
templateUrl: './students.component.html',
styleUrls: ['./students.component.css']
})
export class StudentsComponent implements OnInit {
@ViewChild('studentForm', { static: false })
studentForm: NgForm;
studentData: Student;
dataSource = new MatTableDataSource();
displayedColumns: string[] = ['id', 'name', 'age', 'address', 'actions'];
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
isEditMode = false;
constructor(private httpDataService: HttpDataService) {
this.studentData = {} as Student;
}
ngOnInit(): void {
// Initializing Datatable pagination
this.dataSource.paginator = this.paginator;
// Fetch All Students on Page load
this.getAllStudents()
}
getAllStudents() {
this.httpDataService.getList().subscribe((response: any) => {
this.dataSource.data = response;
});
}
editItem(element) {
this.studentData = _.cloneDeep(element);
this.isEditMode = true;
}
cancelEdit() {
this.isEditMode = false;
this.studentForm.resetForm();
}
deleteItem(id) {
this.httpDataService.deleteItem(id).subscribe((response: any) => {
// Approach #1 to update datatable data on local itself without fetching new data from server
this.dataSource.data = this.dataSource.data.filter((o: Student) => {
return o.id !== id ? o : false;
})
console.log(this.dataSource.data);
// Approach #2 to re-call getAllStudents() to fetch updated data
// this.getAllStudents()
});
}
addStudent() {
this.httpDataService.createItem(this.studentData).subscribe((response: any) => {
this.dataSource.data.push({ ...response })
this.dataSource.data = this.dataSource.data.map(o => {
return o;
})
});
}
updateStudent() {
this.httpDataService.updateItem(this.studentData.id, this.studentData).subscribe((response: any) => {
// Approach #1 to update datatable data on local itself without fetching new data from server
this.dataSource.data = this.dataSource.data.map((o: Student) => {
if (o.id === response.id) {
o = response;
}
return o;
})
// Approach #2 to re-call getAllStudents() to fetch updated data
// this.getAllStudents()
this.cancelEdit()
});
}
onSubmit() {
if (this.studentForm.form.valid) {
if (this.isEditMode)
this.updateStudent()
else
this.addStudent();
} else {
console.log('Enter valid data!');
}
}
}
That’s it now run you server by executing $ json-server --watch ./API/data.json
then run Angular application in another terminal by executing $ ng serve --open
You can get source code of this tutorial in my GitHub repo here.
Conclusion: In this tutorial, we get to know how to use Http services to make server communication, use get, post, put and delete methods on data server. We use RxJs methods and operators to handle errors and network issues using retry()
. Added Angular Material UI library to show data rows in a Datatable. In the Material Datatable, we performed CRUD operation using Inline approach
Leave a Reply