Skip to content

Instantly share code, notes, and snippets.

@juandjara
Last active August 18, 2017 12:18
Show Gist options
  • Save juandjara/08e8e1c6901e43fae86ccef727915bd3 to your computer and use it in GitHub Desktop.
Save juandjara/08e8e1c6901e43fae86ccef727915bd3 to your computer and use it in GitHub Desktop.
Entity reducer with complex crud operations
import angular from 'angular'
export default angular.module('obs.apiMiddleware', [])
.factory('apiMiddleware', ($http, config) => {
'ngInject';
return ({dispatch}) => next => action => {
const {type = "", meta = {}, payload = {}} = action
if(!meta.async) {
return next(action)
}
delete meta.async
if(!/^https?:\/\//.test(payload.url)) {
payload.url = config.api + payload.url
}
const types = {
LOADING: `${type}_LOADING`,
SUCCESS: `${type}_SUCCESS`,
ERROR: `${type}_ERROR`,
}
dispatch({
type: types.LOADING,
payload,
meta
})
return $http(payload)
.then(res => dispatch({
type: types.SUCCESS,
payload: res.data,
meta
}))
.catch(err => dispatch({
type: types.ERROR,
payload: err,
meta,
error: true
}))
}
})
.name
import { combineReducers } from 'redux'
const selectors = {
getEntitiesByBoardId: (state, boardId) => {
const pagination = state.entity.pagination[boardId] || {}
const {loading, page, last, ids} = state.entity.pagination[boardId]
const items = ids.map(id => state.entity.entities[id])
return {items, loading, page, last}
},
getEntityById: (state, id) =>
state.entity.entities[id] || {missing: true}
}
const types = {
ENTITY_FETCH_PAGE: 'ENTITY_FETCH_PAGE',
ENTITY_FETCH: 'ENTITY_FETCH',
ENTITY_CREATE: 'ENTITY_CREATE',
ENTITY_UPDATE: 'ENTITY_UPDATE',
ENTITY_DELETE: 'ENTITY_DELETE'
}
const actions = {
fetchEntitiesPage: (page, size, boardId) => ({
type: types.ENTITY_FETCH_PAGE,
payload: {
method: 'GET',
url: `/entity/board/id/${boardId}`,
params: {page, size}
},
meta: {boardId, async: true}
}),
fetchEntityIfNeeded: id => (dispatch, getState) => {
const entity = selectors.getEntityById(getState(), id)
if(entity.missing || !entity.queries) {
return dispatch(actions.fetchEntity(id))
}
return Promise.resolve({payload: entity})
},
fetchEntity: (id) => ({
type: types.ENTITY_FETCH,
payload: {
method: 'GET',
url: `/entity/id/${id}`
},
meta: {id, async: true}
}),
saveEntity: (entity, isEditMode) => ({
type: isEditMode ? types.ENTITY_UPDATE : types.ENTITY_CREATE,
payload: {
method: isEditMode ? 'PUT' : 'POST',
url: isEditMode ? `/entity/id/${entity.id}` : '/entity',
data: entity
},
meta: {id: entity.id, async: true}
}),
deleteEntity: (entity) => ({
type: types.ENTITY_DELETE,
payload: {
method: 'DELETE',
url: `/entity/id/${entity.id}`
},
meta: {id: entity.id, async: true}
})
}
const arrayToMapById = (arr, idKey = "id") => arr.reduce((prev, next) => {
prev[next[idKey]] = next
return prev
}, {})
const reducer = (state = {}, action = {}) => {
const {type, payload = {}, meta = {}} = action
const id = payload.id || meta.id
switch (type) {
case `${types.ENTITY_FETCH}_LOADING`:
case `${types.ENTITY_UPDATE}_LOADING`:
case `${types.ENTITY_DELETE}_LOADING`:
return {
...state,
[id]: {...state[id], loading: true}
}
case `${types.ENTITY_FETCH_PAGE}_SUCCESS`:
return {
...state,
...arrayToMapById(payload.content.map(entity => ({
...entity,
boardId: meta.boardId
})))
}
case `${types.ENTITY_FETCH}_SUCCESS`:
case `${types.ENTITY_UPDATE}_SUCCESS`:
case `${types.ENTITY_CREATE}_SUCCESS`:
return {
...state,
[id]: payload
}
case `${types.ENTITY_DELETE}_SUCCESS`:
const copy = {...state}
delete copy[id]
return copy
case `${types.ENTITY_FETCH}_ERROR`:
case `${types.ENTITY_UPDATE}_ERROR`:
case `${types.ENTITY_CREATE}_ERROR`:
case `${types.ENTITY_DELETE}_ERROR`:
return {
...state,
[id]: {loading: false, error: payload}
}
default:
return state
}
}
const initialPagination = {
ids: [], page: -1, loading: false, last: false
}
const paginationReducer = (state = initialPagination, action = {}) => {
const {type, payload = {}} = action
switch(type) {
case `${types.ENTITY_FETCH_PAGE}_LOADING`:
return {
...state,
loading: true,
page: payload.params.page,
size: payload.params.size
}
case `${types.ENTITY_FETCH_PAGE}_SUCCESS`:
return {
...state,
loading: false,
page: payload.number,
last: payload.last,
size: payload.size,
ids: state.ids.concat(payload.content.map(elem => elem.id))
}
case `${types.ENTITY_FETCH_PAGE}_ERROR`:
return {
...state,
loading: false,
error: payload
}
default:
return state
}
}
const filterForBoardId = reducer => (state, action = {}) => {
const {meta = {}} = action
if(!meta.boardId) {
return state
}
return {
...state,
[meta.boardId]: reducer(state, action)
}
}
export {types, actions, selectors}
export default combineReducers({
entities: reducer,
pagination: filterForBoardId(paginationReducer)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment