Best practice to use HTTP service
In this article, we will explore the best practices for utilizing an HTTP service. In every application, we interact with backend APIs. Following the principle of DRY (Don’t Repeat Yourself), it is essential to minimize code duplication. I will illustrate the distinction between best practices and suboptimal approaches when using an HTTP service.
The bad Practice of using HTTP service.
import {Injectable} from '@angular/core';
import {DBStores} from '../services/app-cache/cacheable-entities/cache.';
import {DataService} from '../services/data.service';
import {EditFileDTO, Files, FilesProjectTagDTO} from '../shared/interfaces/gallery.interface';
import {LinkFilesDTO} from '../shared/interfaces/task.interface';
import {API_ENDPOINTS, API_URL} from '../shared/Utils/global';
@Injectable({
providedIn: 'root'
})
export class NetworkGalleryService {
constructor(public dataService: DataService) {}
async getGalleryList(): Promise<Files[]> {
let data = (await this.dataService.getListAsync(
DBStores.Files.TableName,
API_URL + API_ENDPOINTS.getPhotoList
)) as Files[];
return data;
}
async uploadMedia(data: any) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.uploadFiles,
data
).toPromise();
}
async uploadMediaNotes(data: any) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.AddUpdateFileNote,
data
).toPromise();
}
async deleteFile(data: Array<string>) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.deletePhoto,
data
).toPromise();
}
async editFiles(data: EditFileDTO) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.editFiles,
data
).toPromise();
}
async bulkFavoriteFiles(body: Array<string>) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.bulkFileFavorite,
body
).toPromise();
}
async linkTagsToFiles(data: FilesProjectTagDTO) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.linkTagsFiles,
data
).toPromise();
}
async linkProjectTypeToFiles(data: FilesProjectTagDTO) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.linkProjectTypeFiles,
data
).toPromise();
}
async linkAttachment(data: LinkFilesDTO[]) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.fileAttachment,
data
).toPromise();
}
async downloadFiles(data: any) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.downloadFiles,
data,
{hideLoader: true}
).toPromise<any>();
}
async manipulateFiles(data:any) {
return await this.dataService.APIService.Post(
API_URL + API_ENDPOINTS.manipulateFile,
data).toPromise()
}
}
you can see how many services are used. every service contains CRUD methods. why need to create multiple service files that make non-sense? why not have only one file that is responsible for CRUD operation?
Pro Tips: we used observable for real-time data and promise for non-real-time data. but this depends upon your requirement as well.
The Best practice of using HTTP service
Create an interface for strong typing. also for intellisense.
//api-base-actions.interface.ts
import {Observable} from 'rxjs';
export type ParamsType = { hideLoader: boolean }
export interface IApiBaseActions {
Get(url: string, params?: ParamsType): Observable<any>;
GetAll(url: string, params?: ParamsType): Observable<any>;
Post(url: string, data: any, params?: ParamsType): Observable<any>;
Delete(url: string, data?: any, params?: ParamsType): Observable<any>;
Put(url: string, data: any, params?: ParamsType): Observable<any>;
}
Create a base service using Abstraction concepts of oops. In this service, we created a blueprint for the CRUD service.
import { Injectable } from "@angular/core";
import { IApiBaseActions, ParamsType } from "../interfaces/api-base-actions.interface";
import { HttpClient, HttpParams } from "@angular/common/http";
import { responseMessage } from "../constants/response.constant";
import { tap } from "rxjs/internal/operators/tap";
@Injectable({
providedIn: 'root'
})
export class ApiHandlerService implements IApiBaseActions {
constructor(public httpClient: HttpClient) {
}
Get(url: string, params?: ParamsType) {
return this.httpClient
.get(url, {params: this.createParams(params)})
.pipe(tap((x) => this.HandleResponse(x)));
}
GetAll(url: string, params?: ParamsType) {
return this.httpClient
.get(url, {params: this.createParams(params)})
.pipe(tap((x) => this.HandleResponse(x)));
}
Post(url: string, data: any, params?: ParamsType) {
return this.httpClient
.post(url, data, {params: this.createParams(params)})
.pipe(tap((x) => this.HandleResponse(x)));
}
Delete(url: string, data:any, params?: ParamsType) {
return this.httpClient
.delete(url, {params: this.createParams(params)})
.pipe(tap((x) => this.HandleResponse(x)));
}
Put(url: string, data: any, params?: ParamsType) {
return this.httpClient
.put(url, data, {params: this.createParams(params)})
.pipe(tap((x) => this.HandleResponse(x)));
}
HandleResponse(response: any) {
if (response.Status === 500) {
alert(responseMessage.serverError);
}
}
createParams(params?: ParamsType) {
let httpParams = new HttpParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
httpParams = httpParams.append(key, value);
});
}
return httpParams;
}
}
Inheriting the base service or using Dependency injection. finally, we have a generic service for CRUD operation that is used throughout the application.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiHandlerService } from './api-handler.service';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class GenericService {
constructor(private apiService: ApiHandlerService) {
}
get(endpoint:string){
return this.apiService.Get(`${AppSettings}`+endpoint)
}
post(endpoint:string,body?:any){
return this.apiService.Post(`${AppSettings}`+endpoint,body)
}
put(endpoint:string,body?:any){
return this.apiService.Put(`${AppSettings}`+endpoint,body)
}
delete(endpoint:string){
return this.apiService.Delete(`${AppSettings}`+endpoint,null)
}
}
In this way, we follow the DRY principle and created a centralized service in our application.
In our component, we only pass the endpoint as argument.
//pass endpoint as argument
this.GenericService.get('personalDevelopmentAreas').subscribe({
next:(response)=>{
this.data = response;
}
})
this.adminService.get('schools').subscribe({
next:(response)=>{
this.data = response;
}
})
}
Conclusion:
In this article, we learned to compare service usage. what are the best and bad practices? we have multiple ways to do this. but the fundamental is to follow the DRY principle.