Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular-cli hmr and ngrx

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:

  • https://github.com/CodeSequence/ngrx-store-hmr (older approach)
  • https://github.com/AngularClass/angular2-hmr (think you need access to webpack config to add loader)

  • Angular 2 : Thoughs about HMR and @ngrx/store (tried the get__HMR__state but didn't work for me)

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

like image 496
Monkeeman69 Avatar asked Jan 16 '17 15:01

Monkeeman69


1 Answers

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

like image 109
MTJ Avatar answered Oct 13 '22 03:10

MTJ