How to Consume REST API in Angular 15

Afif Alfiano
14 min readFeb 18, 2023

With Angular, all utility to consume REST API is ready to use

Angular Page

Hi guys,

Today we will learn about the angular framework and for specific version is version 15. Currently, the demand for angular is increasing time by time, supported by the google team and always updating every six months (if I’m not wrong). In this section, we will focus on how to consume API in angular, services angular, interface, generate with angular cli, reactive forms, bootstrap 5, and many things.

So, if you are still curious about angular just visit the website here

Let’s get started creating project angular.

Pre Installation

I assume that you have installed nodejs and npm, in this tutorial I’m using nodejs version 16.18.1 and npm 8.19.2. But, if you still have not yet installed it, just install it for now.

Installation

We need to install angular cli. Simply, just run this command.

npm i @angular/cli@15.1.16

Init Project

After that, the next step is to init project, just run this command

ng new angular-tutorial-consume-api

Just type y for angular routing, because in the next step we will create a new module. For styling, we are using SCSS and just waiting for the moment.

After initiation is completed, just try to run this command.

npm run start

The preview init project like this

In this tutorial, I want to use API from the link tutorial below, so if you want to try to learn how to create API with NestJS and postgreSQL, just read the article below.

Create Feature

The next step is to create a module todolist, we can use cli angular to generate module, service, component, pipe and etc.

To generate many things we can use in one line like this.

ng generate module todolist && ng generate service todolist/service/todolist && ng g interface todolist/interface/todolist

Oh ya, Don’t forget to generate component todolist

ng generate component todolist/todolist

Configuration Routing

Open app-routing.module.ts and modify the routing.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TodolistComponent } from './todolist/todolist/todolist.component';

const routes: Routes = [
{
title: 'Home',
path: '',
component: TodolistComponent
}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Then, add TodolistModule on app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodolistModule } from './todolist/todolist.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

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

Don’t foget to update app-component.html

<router-outlet></router-outlet>

The function router-outlet is to render component depending on app-routing.module.ts

To use HttpMethod like get, post, update and delete, we need to import HttpClientModule from @angular/common/http , FormsModule, and ReactiveFormsModule to use a reactive form from @angular/forms

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodolistModule } from './todolist/todolist.module';
import { ReactiveFormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms';

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

Interface

Before going to section services, just completed the interface todolist. Open file todolist.interface.ts

export interface Todolist {
id?: number;
title: string;
description: string;
status: string;
due_date: number;
createdAt?: Date;
updatedAt?: Date;
}

Configuration Service

In angular, we’re using a service to declare list of endpoints and or maybe for functions. Open todolist.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable, Type } from '@angular/core';
import { Observable } from 'rxjs';
import { ITodolist } from '../interface/todolist';

@Injectable({
providedIn: 'root'
})
export class TodolistService {
private readonly API_URL = 'http://localhost:3000/todolist';
constructor(
private readonly httpClient: HttpClient
) { }

getAllTodolist(): Observable<ITodolist[]> {
return this.httpClient.get<ITodolist[]>(this.API_URL);
}

getTodolistById(id: number): Observable<ITodolist> {
return this.httpClient.get<ITodolist>(this.API_URL + `?${id}`);
}

createTodolist(body: ITodolist): Observable<ITodolist> {
return this.httpClient.post<ITodolist>(this.API_URL, body);
}

updateTodolist(id: number, body: ITodolist): Observable<ITodolist> {
return this.httpClient.put<ITodolist>(this.API_URL + `/${id}`, body);
}

deleteTodolist(id: number): Observable<Object> {
return this.httpClient.delete<Object>(this.API_URL + `/${id}`);
}


}
  • We need to inject instance HttpClient to function HttpMethod
  • Observable is the data type to stream the data.

In angular, Usually we are using reactive javascript or rxjs to manage or manipulate data. I suggest you to read more information about rxjs in link below.

Testing Todolist Service

We need to make sure that the todolist service is not an error. So, just try on the todolist.component.ts

Please make sure that the services backend was running well

import { AsyncPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { ITodolist } from '../interface/todolist';
import { TodolistService } from '../service/todolist.service';

@Component({
selector: 'app-todolist',
templateUrl: './todolist.component.html',
styleUrls: ['./todolist.component.scss']
})
export class TodolistComponent implements OnInit {

constructor(private readonly todolistService: TodolistService) {}

ngOnInit(): void {
this.todolistService.getAllTodolist().subscribe((response: ITodolist[]) => {
console.log(response, 'res');
})
}

}

After that, check the application on the browser.

If you got an error like the image above, don’t panic, that error is because the backend does not configure CORS. The backend URL is localhost:3000 and frontend localhost:4200, this is the problem because the origin is different. To solve this problem just allow the cors on the backend.

After the backend allows CORS, the response will be like this.

Create User Interface

In this tutorial, I want to use ngx-bootstrap, so just install the library with run this command.

npm i ngx-bootstrap@10.2.0 bootstrap

The version ngx-bootstrap 10.2.0 is using bootstrap 5, So the styling is a little different from previous version. Also, I need fontawesome

npm install @fortawesome/fontawesome-svg-core && 
@fortawesome/free-solid-svg-icons &&
@fortawesome/angular-fontawesome

First of all, we need to update app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodolistModule } from './todolist/todolist.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ModalModule } from 'ngx-bootstrap/modal';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
TodolistModule,
ModalModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
  • From ngx-bootstrap I just import the module modal.

Also in angular.json add these styles.

"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css",
],

Then, update todolist.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TodolistComponent } from './todolist/todolist.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';



@NgModule({
declarations: [
TodolistComponent
],
imports: [
CommonModule,
FontAwesomeModule,
]
})
export class TodolistModule { }
  • We are using fontawesome module to use fontawesome icons.

After that, update file todolist.component.html




<div class="mobile">
<div class="row">
<div class="col-lg-12">
<h3 class="text-center fw-bold mb-3">
My Todolist
</h3>
</div>
</div>
<button type="button" class="btn btn-primary" (click)="createTodo()" style="cursor: pointer">Add Todo</button>
<div class="row my-3">
<div class="col-lg-12">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" aria-label="Search" aria-describedby="search-todolist" #searchInput (keyup)="search(searchInput.value)">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<ul class="list-group">
<ng-container *ngFor="let item of todolist$ | async; let i = index;">
<li class="list-group-item d-flex gap-3 todolist" >
<div class="fw-semibold w-100" (click)="detailTodo(item)">
<span class="pe-1">{{i + 1}}. {{item.title}}</span>
<ng-container [ngSwitch]="item.status">
<span *ngSwitchCase="'progress'" class="badge bg-danger">Progress</span>
<span *ngSwitchCase="'completed'" class="badge bg-primary">Completed</span>
<span *ngSwitchDefault class="badge bg-warning">Todo</span>
</ng-container>
</div>
<div class="d-flex gap-2">
<fa-icon [icon]="faPencil" class="text-success" style="cursor: pointer" (click)="updateTodo(item)"></fa-icon>
<fa-icon [icon]="faTrash" class="text-danger" style="cursor: pointer" (click)="deleteTodo(item)"></fa-icon>
</div>
</li>
</ng-container>
</ul>
</div>
</div>
</div>
  • We are using pipe async to read data observable and automatically unsubscribe data when leaving the page.
  • Using ng switch case to check conditional rendering status todo

Then, update todolist.component.scss





.mobile {
max-width: 576px !important;
margin: 0 auto;
padding: 16px;

.todolist {
&:hover {
background-color: rgb(206, 206, 206);
animation-name: animateChangeBg;
animation-duration: 1s;
}
}
}

@keyframes animateChangeBg {
from {background-color: rgb(255, 255, 255);}
to {background-color: rgb(206, 206, 206);}
}
  • The style above is to make the first mobile-on-desktop views.
  • Of course, we added some animation

Then, update todolist.component.ts

import { AsyncPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { delay, filter, map, Observable } from 'rxjs';
import { ITodolist } from '../interface/todolist';
import { TodolistService } from '../service/todolist.service';
import { faTrash, faPencil } from '@fortawesome/free-solid-svg-icons';
import { DeleteTodolistComponent } from './delete-todolist/delete-todolist.component';
import { FormTodolistComponent } from './form-todolist/form-todolist.component';
import { DetailTodolistComponent } from './detail-todolist/detail-todolist.component';

@Component({
selector: 'app-todolist',
templateUrl: './todolist.component.html',
styleUrls: ['./todolist.component.scss']
})
export class TodolistComponent implements OnInit {
todolist$?: Observable<ITodolist[]>;
searchTodolist = new FormControl();
modalRef?: BsModalRef;
faTrash = faTrash;
faPencil = faPencil;
constructor(private readonly todolistService: TodolistService, private modalService: BsModalService) {}

ngOnInit(): void {
this.getTodolist();
}

getTodolist() {
this.todolist$ = this.todolistService.getAllTodolist();
}

search(searchTodolist: string): void {
this.todolist$ = this.todolistService.getAllTodolist().pipe(
delay(500),
map((todolist: ITodolist[]) => {
const filterData = todolist.filter((item) => {
const checkKeyword = new RegExp(`${searchTodolist}`, 'ig')
if (!item.title.match(checkKeyword)) {
return;
}
return {...item}
})
return filterData;
}),
)
}

createTodo(): void {
this.modalRef = this.modalService.show(FormTodolistComponent, {
initialState: {
data: undefined
},
animated: true,
});
this.modalRef.content.onClose.subscribe((response: boolean) => {
if (response) {
this.getTodolist();
}
})
console.log('create');

}

updateTodo($event: ITodolist): void {
this.modalRef = this.modalService.show(FormTodolistComponent, {
initialState: {
data: $event
},
animated: true,
});
this.modalRef.content.onClose.subscribe((response: boolean) => {
if (response) {
this.getTodolist();
}
})
console.log('update');
}

deleteTodo($event: ITodolist): void {
this.modalRef = this.modalService.show(DeleteTodolistComponent, {
initialState: {
data: $event
},
animated: true,
});
this.modalRef.content.onClose.subscribe((response: boolean) => {
if (response) {
this.getTodolist();
}
})
console.log('delete');
}

detailTodo($event: ITodolist): void {
this.modalRef = this.modalService.show(DetailTodolistComponent, {
initialState: {
data: $event
},
animated: true,
});
}

}
  • We need to inject service todolist on the component todolist to be able to use function hit endpoint like get all, get one and etc.
  • We declare variable todolist$ with type observable to assign function getAll from service todolist.
  • Function search is for searching or filtering data todo by input user and this is the advantage of using rxjs, we can use multiple operators to manipulate data.
  • We need to subscribe content onClose to listen the event, if true, it will auto-call API get all todolist.

The result is like this.

But, we need to add an action like create, detail, update and delete.

For the modal update, create detail, and delete, we will generate a component and separate it from the parent component.

ng generate component todolist/todolist/form-todolist 
ng generate component todolist/todolist/delete-todolist
ng generate component todolist/todolist/detail-todolist

Before that, we will add ngx-toastr to real-time response when any action like delete, create, or update.

npm i ngx-toastr
npm i @angular/animations --save

Then, update app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodolistModule } from './todolist/todolist.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';


@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule,
TodolistModule,
ModalModule.forRoot(),
ToastrModule.forRoot({
timeOut: 2000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
}),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
  • ToastrModule has default timeOut 2s, position at the bottom right and prevents duplicate toastr.

Also in angular.json add these styles.

            "styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/ngx-toastr/toastr.css"
],

Let’s update the component delete modal. Open file delete-modal.component.ts

import { HttpErrorResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrService } from 'ngx-toastr';
import { Observable, Subject } from 'rxjs';
import { ITodolist } from '../../interface/todolist';
import { TodolistService } from '../../service/todolist.service';

@Component({
selector: 'app-delete-todolist',
templateUrl: './delete-todolist.component.html',
styleUrls: ['./delete-todolist.component.scss']
})
export class DeleteTodolistComponent {
public data!: ITodolist;
public onClose: Subject<boolean> = new Subject();
constructor(
private bsModalService: BsModalService,
private todolistService: TodolistService,
private toastrService: ToastrService
) {
}

closeModal(): void {
this.bsModalService.hide();
}

deleteData(): void {
let id = -1;
if (this.data) {
id = this.data.id
}
this.todolistService.deleteTodolist(id).subscribe(response => {
this.toastrService.success('Success', 'Success Delete!');
this.onClose.next(true);
this.closeModal();
}, (err: HttpErrorResponse) => {
this.toastrService.success('Failed', 'Failed Delete!');
throw new Error(err.error);
});
}
}
  • After submit button to delete data, we need to mention on the child component that the component was closed and the value is true with the subject.

Then, open file delete-modal.component.html

<div class="modal-header">
<h4 class="modal-title pull-left">Delete Todo</h4>
<button type="button" class="btn-close close pull-right" aria-label="Close" (click)="closeModal()">
<span aria-hidden="true" class="visually-hidden" (click)="closeModal()">&times;</span>
</button>
</div>
<div class="modal-body">
Are you sure want to delete this todo with the title {{data.title}}?
</div>
<div class="modal-footer">
<button type="button" (click)="closeModal()" class="btn btn-secondary w-25">
Back
</button>
<button type="button" (click)="deleteData()" class="btn btn-danger w-25">Yes</button>
</div>

The preview to use feature delete is like this.

Next, update component detail-toodlist.component.html

<div class="modal-header">
<h4 class="modal-title pull-left">Detail Todo</h4>
<button type="button" class="btn-close close pull-right" aria-label="Close" (click)="closeModal()">
<span aria-hidden="true" class="visually-hidden" (click)="closeModal()">&times;</span>
</button>
</div>
<div class="modal-body">
<table class="table table-borderless">
<tr>
<td>Title</td>
<td>:</td>
<td>{{data.title}}</td>
</tr>
<tr>
<td>Description</td>
<td>:</td>
<td>{{data.description}}</td>
</tr>
<tr>
<td>Status</td>
<td>:</td>
<td>
<ng-container [ngSwitch]="data.status">
<span *ngSwitchCase="'progress'" class="badge bg-danger">Progress</span>
<span *ngSwitchCase="'completed'" class="badge bg-primary">Completed</span>
<span *ngSwitchDefault class="badge bg-warning">Todo</span>
</ng-container>
</td>
</tr>
<tr>
<td>Due Date</td>
<td>:</td>
<td>{{dueDate}}</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" (click)="closeModal()" class="btn btn-secondary w-25">
Back
</button>
</div>

Then, update detail-todolist.component.ts

import { Component, OnInit } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { ITodolist } from '../../interface/todolist';

@Component({
selector: 'app-detail-todolist',
templateUrl: './detail-todolist.component.html',
styleUrls: ['./detail-todolist.component.scss']
})
export class DetailTodolistComponent implements OnInit{
public data!: ITodolist;
public dueDate!: string;
constructor(private bsModalService: BsModalService) {

}

ngOnInit(): void {
this.dueDate = new Date(Number(this.data.due_date)).toLocaleDateString();
}

closeModal(): void {
this.bsModalService.hide();
}
}
  • We need to convert the number of the due date to readable date time.

The preview detail todolist is like this

Next, in the component form-todolist. This component is used for creating and updating todolist.

Open file form-todolist.component.html

<div class="modal-header">
<h4 class="modal-title pull-left">{{title}}</h4>
<button type="button" class="btn-close close pull-right" aria-label="Close" (click)="closeModal()">
<span aria-hidden="true" class="visually-hidden">&times;</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="formTodolist">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" placeholder="Learning something new" formControlName="title">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" rows="3" formControlName="description"></textarea>
</div>
<div class="mb-3">
<select class="form-select" aria-label="Default select example" formControlName="status">
<option selected>Open this select menu</option>
<option value="todo">Todo</option>
<option value="progress">Progress</option>
<option value="completed">Completed</option>
</select>
</div>
<div class="mb-3">
<label for="due_date" class="form-label">Due Date</label>
<input type="date" class="form-control" id="due_date" formControlName="due_date">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" (click)="closeModal()" class="btn btn-secondary w-25">
Back
</button>
<button type="button" (click)="submit()" class="btn btn-danger w-25" [disabled]="formTodolist.invalid">Submit</button>
</div>
  • The modal form is the simple form with element input text, textarea, select, and date.

Open file form-todolist.component.ts

import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrService } from 'ngx-toastr';
import { Subject } from 'rxjs';
import { ITodolist } from '../../interface/todolist';
import { TodolistService } from '../../service/todolist.service';

@Component({
selector: 'app-form-todolist',
templateUrl: './form-todolist.component.html',
styleUrls: ['./form-todolist.component.scss']
})
export class FormTodolistComponent implements OnInit {
public data?: ITodolist;
public title!: string;
public onClose: Subject<boolean> = new Subject();
formTodolist: FormGroup = new FormGroup({
title: new FormControl('', [Validators.required]),
description: new FormControl('', [Validators.required]),
status: new FormControl('todo', [Validators.required]),
due_date: new FormControl('', [Validators.required])
})
constructor(
private bsModalService: BsModalService,
private todolistService: TodolistService,
private toastrService: ToastrService
) {
}
ngOnInit(): void {
if (this.data) {
this.title = 'Update Todo'
this.doInitFormUpdate();
} else {
this.title = 'Create Todo'
}
}

doInitFormUpdate() {
this.formTodolist.setValue({
title: this.data?.title,
description: this.data?.description,
status: this.data?.status,
due_date: this.setDefaultDate(Number(this.data!.due_date))
})
}

setDefaultDate(due_date: number) {
const date = new Date(due_date)
const year = date.getFullYear()

let month: number | string = date.getMonth() + 1
let day: number | string = date.getDate()

if (month < 10) month = '0' + month
if (day < 10) day = '0' + day

const finalDate = `${year}-${month}-${day}`
return finalDate;
}

doCreate(body: ITodolist) {
body.due_date = Number(new Date(this.formTodolist.value.due_date).getTime());
this.todolistService.createTodolist(body).subscribe(response => {
this.toastrService.success('Success', 'Success Create!');
this.onClose.next(true);
this.closeModal();
}, (err: HttpErrorResponse) => {
this.toastrService.error('Failed', 'Failed Create!');
throw new Error(err.error);
})
}

doUpdate(id: number, body: ITodolist) {
body.due_date = Number(new Date(this.formTodolist.value.due_date).getTime());
this.todolistService.updateTodolist(id, body).subscribe(response => {
this.toastrService.success('Success', 'Success Update!');
this.onClose.next(true);
this.closeModal();
}, (err: HttpErrorResponse) => {
this.toastrService.error('Failed', 'Failed Update!');
throw new Error(err.error);
})
}

submit() {
if (this.data) {
this.doUpdate(this.data!.id, this.formTodolist.value);
} else {
this.doCreate(this.formTodolist.value);
}
}



closeModal(): void {
this.bsModalService.hide();
}

}
  • In the form-todolist.component.ts, we combine the function of create and update.
  • When the variable data is undefined it means create mode, otherwise, it means update mode
  • The function setDefaultDate is to set a default value on the element date.
  • doCreate and doUpdate is a function to hit the endpoint by mode form
  • The function doInitFormUpdate is to set a value on update mode.
  • Function submit() is a function to trigger create or update of data.

The preview create data is like this

Then, this is the preview update todolist

Finally, we have completed the tutorial, it’s a little too long ya, but it’s okay because we learn a lot in this tutorial. Anyway for this code, I have push on my GitHub repository

References

#Angular #Angular15 #TutorialAngular #ReactiveForm #ConsumeRestApi #RESTAPI

--

--