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.
More Posts
- Crud Api w/ Server Side Pagination with NestJS & Angular - Initial Setup
- Crud Api w/ Server Side Pagination with NestJS & Angular - Crud Endpoints
- Crud Api w/ Server Side Pagination with NestJS & Angular - Pagination
- Crud Api w/ Server Side Pagination with NestJS & Angular - Angular Generic Service