Hot Module Replacement is reloading whole app instead specific component
Asked Answered
M

2

17

I created a new angular project and set up HMR as explained here: https://github.com/angular/angular-cli/wiki/stories-configure-hmr

The project contains main component (parent) which has router-outlet and links to 3 child components which are lazy loaded.

Note: I am also using custom RouteReuseStrategy but it has no effect on the HMR as far as I tested.

No matter what file I am changing - .html or .ts (parent/children) the whole app reloads.

I've set up a basic repo which can be found here: https://github.com/ronfogel/demo-hmr

Maricruzmaridel answered 13/9, 2018 at 8:11 Comment(2)
With the way it is currently coded, you wouldn't be able to get more than the styles to reload and have some data preserved.Quotation
I have a general function about hot module replacement. In order to do hot module replacement the server side has to support this hot module replacement? Or it can be all handled by client side. I am working on Microsoft ERP system and using Angular. Now everytime I made changes I need to use webpack to compile and transfer files to the IIS directly and it will take some time but if I can implement HRM it will speed up my dev speed drastically.Deimos
H
8

This behavior is expected, I will try to explain what is happening.

Hot module replacement that angular has set up is really just re-bootstraping the whole app in a more generic way and with support for multiple app roots, but if you put the abstractions aside its simply deleting app-root tag, adding it again and bootstraping AppModule again , so the whole app changes:

export const hmrBootstrap = (
  // webpack stuff
  module: any,
  // bootstrap is AppModule bootstrapper 
  bootstrap: () => Promise<NgModuleRef<any>>
) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  // bootstraps AppModule ecery time a HMR is needed
  // sets ngModule equal to AppModule if successful (unnecessary)
  bootstrap().then(mod => (ngModule = mod));
  module.hot.dispose(() => {
    // next two lines get native element for all `app-root` tags
    // that exist in `index.html`
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    // I will share createNewHosts code below it's nothing fancy just
    // the simple add and delete i mentioned
    const makeVisible = createNewHosts(elements);
    //destroy the current AppModule and finalize deletion
    ngModule.destroy();
    makeVisible();
  });
};
Harberd answered 9/10, 2018 at 19:11 Comment(0)
A
6

This is what I use for the latest Angular, working just fine. You can give it a try...

// main.ts
import { bootloader, createInputTransfer, createNewHosts, removeNgStyles } 
    from '@angularclass/hmr/dist/helpers'; // For correct treeshaking

if (environment.production) {
  enableProdMode();
}

type HmrModule<S> = { appRef: ApplicationRef }
type HmrNgrxModule<S, A> = HmrModule<S> & { 
  store: { dispatch: (A) => any } & Observable<S>,
  actionCreator: (s: S) => A
}

const isNgrxModule = <S, A, M extends HmrNgrxModule<S, A>>
  (instance: HmrModule<S> | HmrNgrxModule<S, A>): instance is M =>
    !!((<M>instance).store && (<M>instance).actionCreator);

function processModule<S, A, M extends HmrModule<S> | HmrNgrxModule<S, A>>(ngModuleRef: NgModuleRef<M>) {

  const hot = module['hot'];
  if (hot) {

    hot['accept']();

    const instance = ngModuleRef.instance;
    const hmrStore = hot['data'];

    if (hmrStore) {
      hmrStore.rootState 
        && isNgrxModule(instance) 
        && instance.store.dispatch(instance.actionCreator(hmrStore.rootState));
      hmrStore.restoreInputValues && hmrStore.restoreInputValues();
      instance.appRef.tick();
      Object.keys(hmrStore).forEach(prop => delete hmrStore[prop]);
    }

    hot['dispose'](hmrStore => {
      isNgrxModule(instance) && instance.store.pipe(take(1)).subscribe(s => hmrStore.rootState = s);
      const cmpLocation = instance.appRef.components.map(cmp => cmp.location.nativeElement);
      const disposeOldHosts = createNewHosts(cmpLocation);
      hmrStore.restoreInputValues = createInputTransfer();
      removeNgStyles();
      ngModuleRef.destroy();
      disposeOldHosts();
    });
  }
  else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }

  return ngModuleRef;
}

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);
const hmrBootstrap = () => bootloader(() => bootstrap().then(processModule));

environment.hmr
  ? hmrBootstrap()
  : bootstrap();
// app.module.ts
@NgModule({ ... })
export class AppModule {
  constructor(public appRef: ApplicationRef) { ... }
}

The HMR setup will work with Ngrx store as well, if you are into this sort of stuff. You can omit the Ngrx-handling code, though.

Hope this helps a little :-)

Apocynthion answered 17/9, 2018 at 8:50 Comment(1)
not working for me, if you could provide a working demo it would be great!Maricruzmaridel

© 2022 - 2024 — McMap. All rights reserved.