Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hot Module Replacement is reloading whole app instead specific component

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

like image 253
Ron Avatar asked Sep 13 '18 08:09

Ron


People also ask

What does Hot Module Replacement do?

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways: Retain application state which is lost during a full reload. Save valuable development time by only updating what's changed.

Which command helps to enable hot module replacement in the dev server?

You can use the CLI to modify the webpack-dev-server configuration with the following command: webpack serve --hot-only . Now let's update the index. js file so that when a change inside print. js is detected we tell webpack to accept the updated module.

What is hot module replacement in angular?

HRM is Webpack feature which is also used by Angular11 team since Angular build system is Webpack based. HRM feature enables development faster because it updates limited resource after you save code in project. To enable HMR using Angular 11 cli, following command need to be execute.

What is hot reloading in Webpack?

Developing a JavaScript application involves reloading the browser each time you save code changes in order to refresh the user interface. Developer tools like Webpack can even run in watch mode to monitor your project files for changes.


2 Answers

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();
  });
};
like image 51
Nima Hakimi Avatar answered Oct 26 '22 03:10

Nima Hakimi


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 :-)

like image 45
Heehaaw Avatar answered Oct 26 '22 01:10

Heehaaw