Providing root reducer in @ngrx/store 4.0

Viewed 4866

In @ngrx/store 2.0 we could provide the root reducer as a function and from there we split our logic inside the application. After I updated to @ngrx/store 4.0 I cannot use this feature any more from what I can see the reducers need to be a map of reducers which will create objects under the same keys in the state. Is there a way to use the old behavoir in @ngrx/store 4.0 In my state components are aware one of another and I need to be able to split my state dynamically also I need to be able to dispatch actions to the right reducer in my own way. Also app is splitted in multiple lazy loaded routes which in some cases reuse the data from another feature.

 StoreModule.provideStore(reducer, {
      auth: {
        loggedIn: true
      }
    })

StoreModule.forRoot(reducers, {
      initialState: {
        auth: {
          loggedIn: true
        }
      }
    })

I need reducers to be a function which gets the full state and dispatches it to the correct reducer, Is there a way to achieve this behavior?

4 Answers

You can set up a meta reducer to receive every event and manipulate the state from its root. Here is an example way to set it up:

const myInitialState = {
  // whatever you want your initial state to be
};

export function myMetaReducer(
  reducer: ActionReducer<RootStateType>
): ActionReducer<RootStateType> {
  return function(state, action) {
    if (iWantToHandleThisAction) {
      state = doWhatIWantWith(state);
    }
    return reducer(state, action);
  };
}

@NgModule({
  imports: [
    StoreModule.forRoot(myInitialState, { metaReducers: [myMetaReducer] })
  ]
})
export class AppModule {}

The StoreModule forRoot() function accepts a reducerFactory which can be used as follows:

export function myReducerFactory(reducers: any, initState: any) {
  return (state = myInitialState, action) => myCustomReducer(state, action);
}

@NgModule({
  // ...
  imports: [
    StoreModule.forRoot(null, { reducerFactory: myReducerFactory })
  ]
  // ...
})
export class AppModule {
}

This works for me:

// your old reducer that handled slicing and dicing the state
export function mainReducer(state = {}, action: Action) {
    // ...
    return newState;
}

// new: metaReducer that just calls the main reducer
export function metaReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
    return function (state, action) {
        return MainReducer(state, action);
    };
}

// new: MetaReducer for StoreModule.forRoot()
export const metaReducers: MetaReducer<any>[] = [metaReducer];

// modified: app.module.ts
@NgModule({
    // ...
    imports: [
        // neglect first parameter ActionReducerMap, we don't need this
        StoreModule.forRoot({}, {
            metaReducers: metaReducers,
            initialState: INITIAL_STATE // optional
        }),
    ]
})
Related