Skip to content

Instantly share code, notes, and snippets.

@amcdnl
Last active July 1, 2018 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save amcdnl/cafd37bc9a99e2cd7653ebc023e06ffe to your computer and use it in GitHub Desktop.
Save amcdnl/cafd37bc9a99e2cd7653ebc023e06ffe to your computer and use it in GitHub Desktop.

NGRX Data Abstraction

A higher-order service abstraction for NGRX. It eliminates the boilerplate for CRUD operations with lists.

provideStore({ pizza: entityReducer('pizza') })

const entity = thie.entityService<Pizza>('pizza', this.pizzaService);
entity.query();

const collection$ = this.store.select(state => state.pizza.collection);
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
export interface EntityHttpService<T> {
query: () => Observable<T[]>;
create: () => Observable<T>;
update: () => Observable<T>;
delete: () => {};
}
export interface EntityState {
collection: any[];
loading: boolean;
}
export interface Action {
type: string;
payload?: any;
}
export const initialEntityState = {
collection: [],
loading: false
};
export class EntityService<T> {
constructor(
private _store: Store<any>,
private _name: string,
private _service: EntityHttpService<T>) {}
query() { return this._createHttp('query'); }
create() { return this._createHttp('create'); }
update() { return this._createHttp('update'); }
delete() { return this._createHttp('delete'); }
private _createHttp(method: string) {
const obs = this._service[method]();
this._store.dispatch({
type: `[${method} ${this._name}] ${method} start`
});
obs.subscribe(res => {
this._store.dispatch({
type: `[${method} ${this._name}] ${method} success`,
payload: res
});
}, (err) => {
this._store.dispatch({
type: `[${method} ${this._name}] ${method} error`,
payload: err
});
});
return obs;
}
}
export function entityReducer(name: string, keyProp: string = 'id') {
return function(state: EntityState = initialEntityState, action: Action) {
switch (action.type) {
case `[query ${this._name}] query`:
return { ...state, collection: [], loading: true };
case `[query ${this._name}] query success`:
return { ...state, collection: [...action.payload] };
case `[query ${this._name}] query error`:
return { ...state, loading: false };
case `[create ${this._name}] create`:
return { ...state, collection: [], loading: true };
case `[create ${this._name}] create success`:
return { ...state, collection: [...state.collection, { ...action.payload }] };
case `[create ${this._name}] create error`:
return { ...state, loading: false };
case `[update ${this._name}] update`:
return { ...state, loading: true };
case `[update ${this._name}] update success`:
const updateCollection = [...state.collection];
const updateIdx = updateCollection.findIndex(c => c[keyProp] === action.payload[keyProp]);
if (updateIdx > -1) { updateCollection.splice(updateIdx, 1); }
return { ...state, collection: updateCollection };
case `[update ${this._name}] update error`:
return { ...state, loading: false };
case `[delete ${this._name}] delete`:
return { ...state, loading: true };
case `[delete ${this._name}] delete success`:
const deleteCollection = [...state.collection];
const deleteIdx = deleteCollection.findIndex(c => c[keyProp] === action.payload[keyProp]);
if (deleteIdx > -1) { deleteCollection.splice(deleteIdx, 1); }
return { ...state, collection: deleteCollection };
case `[delete ${this._name}] delete error`:
return { ...state, loading: false };
default:
return state;
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment