diff --git a/src/components/ngRedux.js b/src/components/ngRedux.js index 3795279..b02bc99 100644 --- a/src/components/ngRedux.js +++ b/src/components/ngRedux.js @@ -2,6 +2,7 @@ import Connector from './connector'; import invariant from 'invariant'; import {createStore, applyMiddleware, compose, combineReducers} from 'redux'; import digestMiddleware from './digestMiddleware'; +import {addReducer, removeReducer} from '../utils/dynamicReducers'; import assign from 'lodash.assign'; import curry from 'lodash.curry'; @@ -19,6 +20,7 @@ export default function ngReduxProvider() { let _storeEnhancers = undefined; let _initialState = undefined; let _reducerIsObject = undefined; + let _fixedReducers = undefined; this.createStoreWith = (reducer, middlewares, storeEnhancers, initialState) => { invariant( @@ -62,7 +64,7 @@ export default function ngReduxProvider() { { [key]: getReducerKey(key) } ); - const reducersObj = Object + const reducersObj = _fixedReducers = Object .keys(_reducer) .reduce(resolveReducerKey, {}); @@ -75,10 +77,14 @@ export default function ngReduxProvider() { resolvedMiddleware.push(digestMiddleware($injector.get('$rootScope'))); const store = _initialState - ? applyMiddleware(...resolvedMiddleware)(finalCreateStore)(_reducer, _initialState) - : applyMiddleware(...resolvedMiddleware)(finalCreateStore)(_reducer); - - return assign({}, store, { connect: Connector(store) }); + ? assign({ asyncReducers: {}, fixedReducers: _fixedReducers }, applyMiddleware(...resolvedMiddleware)(finalCreateStore)(_reducer, _initialState)) + : assign({ asyncReducers: {}, fixedReducers: _fixedReducers }, applyMiddleware(...resolvedMiddleware)(finalCreateStore)(_reducer)); + + return assign({}, store, { + addReducer: addReducer(store), + connect: Connector(store), + removeReducer: removeReducer(store) + }); }; this.$get.$inject = ['$injector']; diff --git a/src/utils/dynamicReducers.js b/src/utils/dynamicReducers.js new file mode 100644 index 0000000..c8578af --- /dev/null +++ b/src/utils/dynamicReducers.js @@ -0,0 +1,37 @@ +import assign from 'lodash.assign'; +import curry from 'lodash.curry'; +import invariant from 'invariant'; +import { combineReducers } from 'redux'; + +const typeIs = curry((type, val) => typeof val === type); +const isObject = typeIs('object'); + +function _createReducer(fixedReducers, asyncReducers = {}) { + return combineReducers(assign({}, fixedReducers, asyncReducers)); +} + +export function addReducer(store) { + return (name, reducer) => { + invariant( + isObject(store.fixedReducers), + 'To use async reducers, the reducer parameter passed to createStoreWith must be an Object. Instead received %s.', + typeof store.fixedReducers + ); + + store.asyncReducers[name] = reducer; + store.replaceReducer(_createReducer(store.fixedReducers, store.asyncReducers)); + }; +} + +export function removeReducer(store) { + return (name) => { + invariant( + isObject(store.fixedReducers), + 'To use async reducers, the reducer parameter passed to createStoreWith must be an Object. Instead received %s.', + typeof store.fixedReducers + ); + + delete store.asyncReducers[name]; + store.replaceReducer(_createReducer(store.fixedReducers, store.asyncReducers)); + }; +} diff --git a/test/utils/dynamicReducers.spec.js b/test/utils/dynamicReducers.spec.js new file mode 100644 index 0000000..0927446 --- /dev/null +++ b/test/utils/dynamicReducers.spec.js @@ -0,0 +1,44 @@ +import expect from 'expect'; +import { addReducer, removeReducer } from '../../src/utils/dynamicReducers'; + +describe('Utils', () => { + describe('dynamicReducers', () => { + it('returns a function that adds a new async reducer', () => { + + const fakeStore = { + asyncReducers: {}, + fixedReducers: {}, + replaceReducer: (newReducer) => { + return newReducer; + } + }; + + const result = addReducer(fakeStore); + expect(result).toBeA(Function); + expect(() => result('test', { actionHandler: () => { console.log('hi'); }})).toBeA(Function); + expect(Object.keys(fakeStore.asyncReducers).length).toEqual(0); + result('test', { actionHandler: () => { console.log('hi'); }}); + expect(Object.keys(fakeStore.asyncReducers).length).toEqual(1); + }); + + it('returns a function that removes an existing async reducer', () => { + + const fakeStore = { + asyncReducers: { test: 'a test' }, + fixedReducers: {}, + replaceReducer: (newReducer) => { + return newReducer; + } + }; + + const result = removeReducer(fakeStore); + expect(result).toBeA(Function); + expect(() => result('test')).toBeA(Function); + expect(Object.keys(fakeStore.asyncReducers).length).toEqual(1); + result('fail'); + expect(Object.keys(fakeStore.asyncReducers).length).toEqual(1); + result('test'); + expect(Object.keys(fakeStore.asyncReducers).length).toEqual(0); + }); + }); +});