Angular 8 Creating a Generic Crud Service

Published on

GITHUB: https://github.com/jmw5598/angular-generic-crud-service

 

If you come from a strongly type language background such as Java or C#, you’re probably familiar with the concept of Generics. In this post I’m going to explore using generics to created a reusable crud service in Angular.

When creating a REST API there is generally a repetitive pattern in how paths to endpoints are created. For example we commonly use the same path as a base changing only the HTTP method to perform different operations. This makes API’s very predictable and easy to follow. We can take advantage of this predictability and write code in a more reusable way.

A common naming pattern in REST APIs is to have a path with the resource name as a base to retrieve a collection of that resource or including a resource’s identifier as a path parameter to target a specific resource by it’s Id.

 

Path Method Description
/api/resource GET Returns a list of bookmarks
/api/resource POST Creates a new bookmark
/api/resource/:id GET Get a bookmark by it’s Id
/api/resource/:id PUT Updates a bookmark by it’s Id
/api/resource/:id DELETE Deletes a bookmark by it’s Id

 

Our CRUD service will apply to the above path schema. The first thing we need to do is create an interface declaring our abstract methods. You can name these whatever you choose but I’m going to mimic Spring Frameworks’s CrudRepository as this was my influence behind this post.

import { Observable } from 'rxjs/Observable';

export interface CrudOperations<T, ID> {
  save(t: T): Observable<T>;
  update(id: ID, t: T): Observable<T>;
  findOne(id: ID): Observable<T>;
  findAll(): Observable<T[]>;
  delete(id: ID): Observable<any>;
}

Now lets implement this interface as a abstract class that we will be extending later on.

import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { CrudOperations } from './crud-operations.interface';

export abstract class CrudService<T, ID> implements CrudOperations<T, ID> {

  constructor(
    protected _http: HttpClient,
    protected _base: string
  ) {}

  save(t: T): Observable<T> {
    return this._http.post<T>(this._base, t);
  }

  update(id: ID, t: T): Observable<T> {
    return this._http.put<T>(this._base + "/" + id, t, {});
  }

  findOne(id: ID): Observable<T> {
    return this._http.get<T>(this._base + "/" + id);
  }

  findAll(): Observable<T[]> {
    return this._http.get<T[]>(this._base)
  }

  delete(id: ID): Observable<T> {
    return this._http.delete<T>(this._base + '/' + id);
  }

}

Our resource type, a Bookmark.

export class Bookmark {
  public id: number;
  public url: string;
  public description: string;
}

Our BookmarkService that extends our abstract CrudService<T, ID>.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Bookmark } from './bookmark.model';
import { CrudService } from './crud.service';

import { environment } from '../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class BookmarkService extends CrudService<Bookmark, number> {

  constructor(protected _http: HttpClient) {
    super(_http, `${environment.api.baseUrl}/bookmarks`);
  }

}

We can add our base url to our environemnts/environment.ts file.

export const environment = {
  production: false,
  api: {
    baseUrl: 'http://localhost:8080/api'
  }
};

 

The above BookmarkService has methods that map to the follow paths.

BookmarkService Methods Path Method
findAll() /api/bookmarks GET
save(bookmark: Bookmark) /api/bookmarks POST
findById(id: number) /api/bookmarks/:id GET
update(id: number, bookmark: Bookmark) /api/bookmarks/:id PUT
delete(id: number, bookmark: Bookmark) /api/bookmarks/:id DELETE

 

We can use this generic crud service to apply crud operations to any of your resource types.

 

UPDATE 10/20/2020

A question was asked in the comments pertaining to applying this to a paginated endpoint. Applying this to pagination is heavily dependant on how the pagination is implemented. I’ve recently created a small series of posts where we create a paginated endpoint with NestJS and apply this same generic crud pattern to it.

 

 

comments powered by Disqus