Last active
August 18, 2017 12:18
-
-
Save juandjara/08e8e1c6901e43fae86ccef727915bd3 to your computer and use it in GitHub Desktop.
Entity reducer with complex crud operations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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