From d2295c78a29635d40b70b44155d7f0e783310c0b Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 18 Jul 2017 06:44:25 -0500 Subject: [PATCH] feat(Store): Simplify API for adding meta-reducers (#87) --- docs/store/api.md | 10 +++++----- example-app/app/app.module.ts | 4 ++-- example-app/app/reducers/index.ts | 20 +++++++++++++++++++- modules/store/src/index.ts | 4 +++- modules/store/src/models.ts | 1 + modules/store/src/reducer_manager.ts | 8 ++++++-- modules/store/src/store_module.ts | 17 +++++++++++++++-- modules/store/src/tokens.ts | 4 ++++ modules/store/src/utils.ts | 11 +++++++++++ 9 files changed, 66 insertions(+), 13 deletions(-) diff --git a/docs/store/api.md b/docs/store/api.md index 58167914d7..56b47dd6b1 100644 --- a/docs/store/api.md +++ b/docs/store/api.md @@ -45,8 +45,8 @@ export function getInitialState() { ## Reducer Factory -@ngrx/store composes your map of reducers into a single reducer. Use the `reducerFactory` -configuration option to provide a composed action reducer factory: +@ngrx/store composes your map of reducers into a single reducer. Use the `metaReducers` +configuration option to provide an array of meta-reducers that are composed from right to left. ```ts import { StoreModule, combineReducers, compose } from '@ngrx/store'; @@ -62,11 +62,11 @@ function debug(reducer) { } } -const debugReducerFactory = compose(debug, combineReducers); +const metaReducers = [debug]; @NgModule({ imports: [ - StoreModule.forRoot(reducers, { reducerFactory: debugReducerFactory }) + StoreModule.forRoot(reducers, { metaReducers }) ] }) export class AppModule {} @@ -77,7 +77,7 @@ export class AppModule {} Store uses fractal state management, which provides state composition through feature modules, loaded eagerly or lazily. Provide feature states using the `StoreModule.forFeature` method. This method defines the name of the feature state and the reducers that make up the state. The same `initialState` -and `reducerFactory` configuration options are available. +and `metaReducers` configuration options are available. ```ts // feature.module.ts diff --git a/example-app/app/app.module.ts b/example-app/app/app.module.ts index 352a2662ad..d69e16b660 100644 --- a/example-app/app/app.module.ts +++ b/example-app/app/app.module.ts @@ -16,7 +16,7 @@ import { CoreModule } from './core/core.module'; import { AuthModule } from './auth/auth.module'; import { routes } from './routes'; -import { reducers } from './reducers'; +import { reducers, metaReducers } from './reducers'; import { schema } from './db'; import { AppComponent } from './core/containers/app'; @@ -37,7 +37,7 @@ import { environment } from '../environments/environment'; * meta-reducer. This returns all providers for an @ngrx/store * based application. */ - StoreModule.forRoot(reducers), + StoreModule.forRoot(reducers, { metaReducers }), /** * @ngrx/router-store keeps router state up-to-date in the store. diff --git a/example-app/app/reducers/index.ts b/example-app/app/reducers/index.ts index 7c5f4e156b..9ad912c1af 100644 --- a/example-app/app/reducers/index.ts +++ b/example-app/app/reducers/index.ts @@ -2,7 +2,6 @@ import { ActionReducerMap, createSelector, createFeatureSelector, - compose, ActionReducer, combineReducers, Action, @@ -37,6 +36,25 @@ export const reducers: ActionReducerMap = { layout: fromLayout.reducer, }; +// console.log all actions +export function logger(reducer: ActionReducer): ActionReducer { + return function(state: State, action: any): State { + console.log('state', state); + console.log('action', action); + + return reducer(state, action); + }; +} + +/** + * By default, @ngrx/store uses combineReducers with the reducer map to compose + * the root meta-reducer. To add more meta-reducers, provide an array of meta-reducers + * that will be composed to form the root meta-reducer. + */ +export const metaReducers: ActionReducer[] = !environment.production + ? [logger] + : []; + /** * Layout Reducers */ diff --git a/modules/store/src/index.ts b/modules/store/src/index.ts index 7de2025aaa..d81d674b07 100644 --- a/modules/store/src/index.ts +++ b/modules/store/src/index.ts @@ -7,7 +7,7 @@ export { } from './models'; export { StoreModule } from './store_module'; export { Store } from './store'; -export { combineReducers, compose } from './utils'; +export { combineReducers, compose, createReducerFactory } from './utils'; export { ActionsSubject, INIT } from './actions_subject'; export { ReducerManager, @@ -24,10 +24,12 @@ export { export { State, StateObservable, reduceState } from './state'; export { INITIAL_STATE, + _REDUCER_FACTORY, REDUCER_FACTORY, INITIAL_REDUCERS, STORE_FEATURES, _INITIAL_STATE, + META_REDUCERS, } from './tokens'; export { StoreRootModule, diff --git a/modules/store/src/models.ts b/modules/store/src/models.ts index 412de203b3..4c5c4c324f 100644 --- a/modules/store/src/models.ts +++ b/modules/store/src/models.ts @@ -26,6 +26,7 @@ export interface StoreFeature { reducers: ActionReducerMap | ActionReducer; reducerFactory: ActionReducerFactory; initialState?: InitialState; + metaReducers?: ActionReducer[]; } export interface Selector { diff --git a/modules/store/src/reducer_manager.ts b/modules/store/src/reducer_manager.ts index 1b3a1f3d81..66a3dbf301 100644 --- a/modules/store/src/reducer_manager.ts +++ b/modules/store/src/reducer_manager.ts @@ -9,7 +9,7 @@ import { StoreFeature, } from './models'; import { INITIAL_STATE, INITIAL_REDUCERS, REDUCER_FACTORY } from './tokens'; -import { omit } from './utils'; +import { omit, createReducerFactory } from './utils'; import { ActionsSubject } from './actions_subject'; export abstract class ReducerObservable extends Observable< @@ -34,13 +34,17 @@ export class ReducerManager extends BehaviorSubject> addFeature({ reducers, reducerFactory, + metaReducers, initialState, key, }: StoreFeature) { const reducer = typeof reducers === 'function' ? reducers - : reducerFactory(reducers, initialState); + : createReducerFactory(reducerFactory, metaReducers)( + reducers, + initialState + ); this.addReducer(key, reducer); } diff --git a/modules/store/src/store_module.ts b/modules/store/src/store_module.ts index a246e92f01..995aaf960f 100644 --- a/modules/store/src/store_module.ts +++ b/modules/store/src/store_module.ts @@ -13,13 +13,15 @@ import { StoreFeature, InitialState, } from './models'; -import { combineReducers } from './utils'; +import { compose, combineReducers, createReducerFactory } from './utils'; import { INITIAL_STATE, INITIAL_REDUCERS, REDUCER_FACTORY, + _REDUCER_FACTORY, STORE_FEATURES, _INITIAL_STATE, + META_REDUCERS, } from './tokens'; import { ACTIONS_SUBJECT_PROVIDERS, ActionsSubject } from './actions_subject'; import { @@ -62,6 +64,7 @@ export class StoreFeatureModule implements OnDestroy { export type StoreConfig = { initialState?: InitialState; reducerFactory?: ActionReducerFactory; + metaReducers?: ActionReducer[]; }; @NgModule({}) @@ -89,11 +92,20 @@ export class StoreModule { ? { provide: INITIAL_REDUCERS, useExisting: reducers } : { provide: INITIAL_REDUCERS, useValue: reducers }, { - provide: REDUCER_FACTORY, + provide: META_REDUCERS, + useValue: config.metaReducers ? config.metaReducers : [], + }, + { + provide: _REDUCER_FACTORY, useValue: config.reducerFactory ? config.reducerFactory : combineReducers, }, + { + provide: REDUCER_FACTORY, + deps: [_REDUCER_FACTORY, META_REDUCERS], + useFactory: createReducerFactory, + }, ACTIONS_SUBJECT_PROVIDERS, REDUCER_MANAGER_PROVIDERS, SCANNED_ACTIONS_SUBJECT_PROVIDERS, @@ -130,6 +142,7 @@ export class StoreModule { reducerFactory: config.reducerFactory ? config.reducerFactory : combineReducers, + metaReducers: config.metaReducers ? config.metaReducers : [], initialState: config.initialState, }, }, diff --git a/modules/store/src/tokens.ts b/modules/store/src/tokens.ts index 45811564c4..3601686859 100644 --- a/modules/store/src/tokens.ts +++ b/modules/store/src/tokens.ts @@ -3,5 +3,9 @@ import { OpaqueToken } from '@angular/core'; export const _INITIAL_STATE = new OpaqueToken('_ngrx/store Initial State'); export const INITIAL_STATE = new OpaqueToken('@ngrx/store Initial State'); export const REDUCER_FACTORY = new OpaqueToken('@ngrx/store Reducer Factory'); +export const _REDUCER_FACTORY = new OpaqueToken( + '@ngrx/store Reducer Factory Provider' +); export const INITIAL_REDUCERS = new OpaqueToken('@ngrx/store Initial Reducers'); +export const META_REDUCERS = new OpaqueToken('@ngrx/store Meta Reducers'); export const STORE_FEATURES = new OpaqueToken('@ngrx/store Store Features'); diff --git a/modules/store/src/utils.ts b/modules/store/src/utils.ts index a5eb0476f4..a8a30c7642 100644 --- a/modules/store/src/utils.ts +++ b/modules/store/src/utils.ts @@ -83,3 +83,14 @@ export function compose(...functions: any[]) { return rest.reduceRight((composed, fn) => fn(composed), last(arg)); }; } + +export function createReducerFactory( + reducerFactory: ActionReducerFactory, + metaReducers?: ActionReducer[] +): ActionReducerFactory { + if (Array.isArray(metaReducers) && metaReducers.length > 0) { + return compose.apply(null, [...metaReducers, reducerFactory]); + } + + return reducerFactory; +}