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 add –dev @angularclass/hmr

// or with 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';

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.