Search code examples
angularngrxangular-universalngrx-store

angular universal after import server state, @ngrx/store/update-reducers action wipes store


In angular universal project in ssr mode after successfully importing transferred state into store, next dispatched action @ngrx/store/update-reducers wipes the store. Actually there are multiple @ngrx/store/update-reducers actions fired (>10).

Inspecting in @ngrx/store-devtools (chrome extension) shows incorrectly that store after @ngrx/store/update-reducers is still filled with data but it's not true, I see that previously loaded data in cmp disappears after a quick moment (when this mentioned action fires).

It only happens in ssr mode, although multiple @ngrx/store/update-reducers are still present in @ngrx/store-devtools in classic ng serve.

deps: angular 5.2.7, @ngrx/{store,effects,store-devtools,router-store} 5.2.0, chrome 66


Solution

  • Usually @ngrx/store/update-reducers adds reducers from lazy loaded feature modules. Thus I assume that in your case multiple feature modules are lazy loaded.

    What most probably happens is that transferred state is set before reducers for lazy loaded modules are added. By default ngrx will clear all parts of state that have no reducers assigned (this is done in combineReducers function which runs on every action dispatched).

    Possible solutions:

    1. Assign default reducers in root module for feature modules

    StoreModule.forRoot(initialReducerMap, { initialState: getInitialState })
    
    export function defaultReducer(state) { return state; }
    
    export const initialReducerMap = {
        // this will make sure `feature1` and `feature2` parts are not cleared on next action
        feature1: defaultReducer,
        feature2: defaultReducer
    } as ActionReducerMap<ApplicationState>;
    
    
    export function getInitialState() {
        return {
            feature1: feature1.initialState,
            feature2: feature2.initialState
        };
    }
    

    More info in this blog post

    2. Manually set transferred state parts for lazy loaded modules

    import { ActionReducer, UPDATE } from "@ngrx/store";
    
    let transferedState: any;
    export function stateSetter(reducer: ActionReducer<any>): ActionReducer<any> {
      return function(state: any, action: any) {
        if (action.type === 'SET_ROOT_STATE') {
          transferedState = action.payload;
          return action.payload;
        }
    
        // on "update-reducers" set their initial transfered state
        if (action.type === UPDATE && transferedState && action.features) {
            const features: string[] = (action as any).features;
            const newState = { ...state };
            for (const feature of features) {
                newState[feature] = newState[feature] || transferedState[feature];
            }
            return reducer(newState, action);
        }
    
        return reducer(state, action);
      };
    }
    

    Other

    Refer to this github issue