Enabling Hot Module Replacement (HMR) in Angular 6

Hot Module Replacement (HMR) is a key webpack feature that is not enable by default in Angular. It allows for modules to be replaced without need for a full browser refresh. This allows you to retain much of the application state, usually lost when reloading. It also saves valuable times by only updating what has changed. And changes to the code reflect almost instantly, like when you are messing with dev tools on the browser. This is great for productivity.

In this post, I will show you how to add Hot Module Replacement to your angular project. We will add it as an extra tool for you, without taking away the tools you are already used to such as ng serve. If you find it useful, you can continue using it and if not, nothing changes, you can fallback to your old tools.

To enable HMR, we are going to make some modification to our code and add a new dev dependency.

Let’s gets started:

Installing Hot Module Replacement Dependency for Angular

First, we are going to install @angularclass/hmr dev package:

Yarn:

yarn add –dev @angularclass/hmr

NPM:

npm install --save-dev @angularclass/hmr

Next, we need to add an extra environment for our angular application, specifically for HMR. On top of that, we need to modify our existing environments and set HMR to false. You can learn more about environment variables here.

Adding HMR Environment

Create a new environment file called environment.hmr.ts, under src/environment directory at the root of your workspace. Add the following code:

export const environment = {
  production: false,
  hmr: true
};

Please note, we added an extra HMR property for our environment and set it to true.

After these, we need to modify our angular configuration and add this environment for both our build and serve properties. Open angular.json at the root of your angular workspace. Under build property, add a new configuration for HMR, with the content below:

"build": {
    "configurations": {
      ...
      "hmr": {
        "fileReplacements": [
          {
            "replace": "src/environments/environment.ts",
            "with": "src/environments/environment.hmr.ts"
          }
        ]
      }
    }
  },

And then under serve property, add another HMR configuration with the content below:

"serve": {
    "configurations": {
      ...
      "hmr": {
        "hmr": true,
        "browserTarget": "<project-name>:build:hmr"
      }
    }
  }

Next up, modify your typescript configurations (src/tsconfig.app.json), to add Node to the typings array.

{
  ...
  "compilerOptions": {
    ...
    "types": ["node"]
  },
}

Updating Existing Environments

Next, we need to modify our existing environment files, and set the HMR property to false. So, inside our environment.ts and environment.prod.ts,  add the hmr property and set it to false. Do the same for all other extra environments you may have in your app.

// environment.prod.ts
export const environment = {
  production: true,
  hmr: false
};

// environment.ts
export const environment = {
  production: false,
  hmr: false
};

Configure your App to Use HMR

Next up, we need to configure our angular app to use Hot Module Replacement. If you have an Angular Workspace with multiple projects/apps, you will have to do this for each app.

First, create a new file – src/hmr.ts – next to the src/main.ts file and add the following code:

import { NgModuleRef, ApplicationRef } from '@angular/core';

import { createNewHosts } from '@angularclass/hmr';

import { NgModuleRef, ApplicationRef } from '@angular/core';

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  bootstrap().then(mod => (ngModule = mod));
  module.hot.dispose(() => {
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    const makeVisible = createNewHosts(elements);
    ngModule.destroy();
    makeVisible();
  });
};

Then, modify src/main.ts file to use src/hmr.ts code when serving your app with Hot Module Replacement enabled. This modification will only apply when you serve your app with –configuration hmr flag and not under any other circumstances.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { hmrBootstrap } from './hmr';

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

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.hmr) {
  if (module[ 'hot' ]) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }
} else {
  bootstrap().catch(err=>console.log(err));
}

Serving your App with HMR Enabled

Now, you can serve your app using the command below:

ng serve --configuration hmr

But since this is a long command to write every time, we can add it to our npm scripts in our package.json to make it shorter and easier to remember:

"scripts": {
  ...
  "hmr": "ng serve --configuration hmr"
}

And finally, we can just run:

npm run hmr

Each time you run the command, you will get the following warning:

NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.

With some more information on where to learn more about Hot Module Replacement.

Enabling HMR (Hot Module Replacement) in Angular 6

16 Replies to “Enabling Hot Module Replacement (HMR) in Angular 6”

  1. Hi Maina,
    I have few doubts after implementing the HRM with Angular. Requesting you to answer it please.

    Is HMR supposed to maintain the application state also or it’s just reload the application without refreshing the browser ?
    What i need to do, if i need to maintain the application state with HMR ? Any example would be a great help.
    I guess implementation of HMR should be done only while doing development. Does it make any sense to use HMR at QA/Prod environment ?

    TIA

    1. Hi, can you give about a week, so I can work on a demo. A lot of people have requested for such a demo but I haven’t had time to do it.

  2. FYI, you have an error in your hmr.ts file where you import duplicate identifiers:

    import { NgModuleRef, ApplicationRef } from ‘@angular/core’;

    import { createNewHosts } from ‘@angularclass/hmr’;

    import { NgModuleRef, ApplicationRef } from ‘@angular/core’;

    import { createNewHosts } from ‘@angularclass/hmr’;

  3. I followed these instructions on the Tour of Heroes starter project. If I make a change to any child component (such as messages.component.ts or messages.component.html), the whole app reloads (see the console output below). I’m not as concerned about losing state as the fact that the entire app reloads. It seems to completely defeat the purpose of HMR. Are you seeing something different? Do you have a working example on github?

    [HMR] Checking for updates on the server…
    main.ts:16 hot module replacement enabled
    log.js:24 [HMR] Updated modules:
    log.js:24 [HMR] – ./src/app/messages/messages.component.html
    log.js:24 [HMR] – ./src/app/messages/messages.component.ts
    log.js:24 [HMR] – ./src/app/app.module.ts
    log.js:24 [HMR] – ./src/main.ts
    log.js:24 [HMR] App is up to date.

  4. So I have found a problem with this setup. If you edit the typescript files hot module replacement works just fine. The state is kept alive etc. But if you edit the view in any way the whole app has to refresh losing state.

    1. This feature must be quite erratic. I haven’t had an experience to yourself, but it is also not smooth sailing either on my part either. I am working on a demo, which should be ready by the end of the week, but I can’t assure you it will work for you as well as you want to.

  5. I tried your steps. The HMR is configured correctly. But I still losing Local State when updating component template. Do you know why?

        1. You have setup HMR correctly. The reason you are loosing state is when you update a component, angular converts all component related files into a single javascript file and hence the reason you are loosing state in your component. From what I can assert, as long as you are editing the component, that specific component and children component will lose state as its being update. Parent components will not be affected.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.