6

I am currently using an angular-cli project(1.0.0-beta.25.5) with ngrx to manage state. I have followed this article and managed to get hot module replacement working however I have not found a way to maintain state when this happens.

I have seen the following but have been unable to get anything working or take inspiration:

Does anyone have any ideas or suggestions on how to approach this? I wish to remain using the cli so need to find a way of integrating with this.

Edit: Found someone with the same issue here as well https://github.com/ngrx/store/issues/311

Community
  • 1
  • 1
Monkeeman69
  • 502
  • 2
  • 5
  • 18
  • I have implemented a temporary solution by using https://github.com/btroncone/ngrx-store-localstorage where you can select all or partial state to put in local storage. You can elect to rehydrate from local storage in the settings, therefore when hmr kicks in state is picked back up. This doesn't however answer how I can do this in code without relying on local storage. – Monkeeman69 Jan 24 '17 at 10:35

1 Answers1

2

I know this is necromancy ;P But for some this still might be useful.

TL;DR

What you missed from angular-class HMR was quite likely metareducer for setting the complete state.

Below is how I implemented HMR with link to example from which I derived this https://github.com/gdi2290/angular-hmr

Metareducer

First you need a metareducer to handle setting whole state.

// make sure you export for AoT
export function stateSetter(reducer: ActionReducer<any>): ActionReducer<any> {
  return function(state: any, action: any) {
    if (action.type === 'SET_ROOT_STATE') {
      return action.payload;
    }
    return reducer(state, action);
  };
}

let _metaReducers: MetaReducer<fromRoot.State, any>[] = [];
if (environment.hmr) {
  _metaReducers = [stateSetter];
}

export const metaReducers = _metaReducers;

When registering StoreModule.forRoot for NgModule remember to register that metareducer array.

StoreModule.forRoot(reducers, { metaReducers })

AppModule

For the AppModule you need to define hmrOnInit , hmrOnDestroy & hmrAfterDestroy methods.

  • hmrOnInit loads the state
  • hmrOnDestroy writes the state ( note ngrx store.take(1) is really synchronous it's listed somewhere in the ngrx github issues, can't seem to find where atm ).
  • hmrAfterDestroy cleans up existing component elements
export class AppModule {
  constructor(
    private appRef: ApplicationRef,
    private store: Store<fromRoot.State>
  ) { }

  public hmrOnInit(store) {
    if (!store || !store.state) {
      return;
    }
    // restore state
    this.store.dispatch({ type: 'SET_ROOT_STATE', payload: store.state });
    // restore input values
    if ('restoreInputValues' in store) {
      const restoreInputValues = store.restoreInputValues;
      // this isn't clean but gets the job done in development
      setTimeout(restoreInputValues);
    }
    this.appRef.tick();
    Object.keys(store).forEach(prop => delete store[prop]);
  }

  public hmrOnDestroy(store) {
    const cmpLocation = this.appRef.components.map(
      cmp => cmp.location.nativeElement
    );
    let currentState: fromRoot.State;
    this.store.take(1).subscribe(state => (currentState = state));
    store.state = currentState;
    // recreate elements
    store.disposeOldHosts = createNewHosts(cmpLocation);
    // save input values
    store.restoreInputValues = createInputTransfer();
    // remove styles
    removeNgStyles();
  }

  public hmrAfterDestroy(store) {
    // display new elements
    store.disposeOldHosts();
    delete store.disposeOldHosts;
  }
}

For more specific information see https://github.com/gdi2290/angular-hmr

MTJ
  • 1,059
  • 1
  • 10
  • 23