avatarLovepreet Singh

Summary

The article provides a step-by-step guide on implementing Module Federation in an Angular application using Standalone Components, ensuring seamless integration and sharing of modules between a host and remote applications.

Abstract

This article is the third installment in a series dedicated to Module Federation, focusing on its application within Angular. It guides developers through a six-step process to integrate Module Federation in an Angular application using Standalone Components. The process begins with setting up a custom Webpack configuration to enable Module Federation, followed by configuring both the remote and host applications to expose and consume shared modules. The article emphasizes the importance of the bootstrap pattern for proper library loading and addresses potential issues such as multiple Angular version conflicts. By the end of the guide, developers will have a working example of a notes app that loads a customer app remotely, leveraging the power of Module Federation to share code dynamically between Angular applications.

Opinions

  • The author believes that Module Federation is a powerful tool for code sharing in micro-frontend architectures.
  • It is suggested that using the custom-webpack builder is essential for integrating Module Federation into Angular applications.
  • The author highlights the necessity of sharing core Angular libraries to prevent version conflicts and ensure smooth operation between federated modules.
  • The article implies that the dynamic import feature of JavaScript, combined with Module Federation, provides a seamless way to access remote modules.
  • The author recommends using a remotes.d.ts file to declare remote types for TypeScript compatibility, indicating a common challenge faced by developers in this setup.
  • The article promotes the use of Standalone Components in Angular as a modern approach to building applications, which aligns with the latest Angular versions and best practices.
  • By providing a comprehensive guide, the author conveys confidence in the practicality and benefits of adopting Module Federation in Angular projects.

Module Federation using Angular Standalone Components — In 6 Steps

Photo by Arnaud Weyts on Unsplash

This is part 3 of the series on Module Federation.

  1. Module Federation Concepts: Clearly Explained with an Example
  2. Module Federation using Angular Standalone Components — In 6 Steps
  3. Dynamic Module Federation in Angular

This article thoroughly explores the module federation in Angular using Standalone Components from the ground up to understand how it works with an Angular application. We will start with a newly generated angular application and add module federation step-by-step.

This application examples and code are generated using Angular version 17.

You can find the source code used in this app here.

Step 0 — Our App Preview

We have a simple notes app with only two tabs: Notes and Customer. The app containing both notes and customers is the shell or the host app. However, the customer is an app(remote) in itself. The host uses the module federation to load the customer app and use it in its app.

Host: http://locahost:4200 Remote: http://locahost:4201

Host: Contains the notes features and using the remote customer app
The customer app is a remote app that the host is consuming

You can use your apps if you want to do hands-on or use the source code to understand the flow with steps.

Step 1— Custom Webpack Config

The module federation is being implemented using a plugin called ModuleFederationPlugin by Webpack and is not available by default in an Angular application. Therefore, the first step is to make our application accept a custom Webpack config where we can add the module federation plugin.

To do so we have to use the custom-webpack builder. This will allow us to add the custom Webpack config using a file.

npm i -D @angular-builders/custom-webpack

Next, we need to make the following changes. - Replace build’s builder with custom-webpack:browser - Replace serve’s builder with custom-webpack:dev-server - Add path to the custom Webpack file in the options under customWebpackConfig - Change the “browser” key under options to “main”

...
"architect": {
    "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        "options": {
            "main": "./src/main.ts",
            "customWebpackConfig": {
              "path": "./custom-webpack.config.js"
            },
        }
        ...
    },
    "serve": {
        "builder": "@angular-builders/custom-webpack:dev-server",
        ...
    }
}

This step is needed for both the remote and the host.

However, an extra step is needed for the remote to solve an issue that comes when both the host and the remote are running together using “ng serve” or the dev server.

...
"architect": {
    ...
    "serve": {
      ...
      "options": {
        "publicHost": "http://localhost:4201"
      },
    }
}

If we don’t set the above option, the host will keep refreshing frequently, getting constant updates from the remote.

Step 2— Adding Webpack Config for Remote

In this step, we will add the module federation plugin in the custom Webpack file for the remote. Some extra config options are in the same file, but I have added comments to explain those here to keep this article short.

Remember, the remote will expose something that other apps can use. name: The unique name of this module federation app. filename: The generated remote entry file for the remote. This will be used by the host to excess exposed modules. exposes: The modules we want to expose from this app. The key is the name given to the exposed module and the value is the module’s path. shared: The libraries we are sharing with other apps.

I have kept the shared config empty to explain some concepts. We will fill this in a bit later. We are sharing our customer standalone component.

// custom-webpack config
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // Rest of the config
  plugins: [
    new ModuleFederationPlugin({
      name: "angular-mfe-remote", // A unique name for this remote app
      filename: "remoteEntry.js", // 
      exposes: {
        "./Test": "./src/app/customer/customer.component.ts",
      },
      library: {
        type: "module",
      },
      shared: {}
    }),
  ],
}

After this step, if you run the “npm run build” command, there will be a remoteEntry.js file generated as part of the build.

That is all for the remote! module federation will take care of everything behind the scenes and expose this module to other apps.

Step 3— Adding Webpack Config for Host

Now we will repeat the same step for the host to consume the modules exposed by the remote.

The host has the same config except instead of exposes, it has remotes. remotes: The modules that the host wants to consume. The key is the local name that the host is giving to the remote and value is the URL of the remote entry file of the remote.

const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // Rest of the config
  plugins: [
    new ModuleFederationPlugin({
      name: "angular-mfe-host",
      filename: "remoteEntry.js",
      remotes: {
        remote: "http://localhost:4201/remoteEntry.js",
      },
      shared: {
      },
      library: {
        type: "module",
      },
    }),
  ],
};

Step 4— Implementing Bootstrap Pattern

We know in an Angular project, we have a main.ts file that is the entry point to our application and it contains the code to bootstrap our application.

We have to change it a bit for module federation as shared libraries are not available if we load and run our main application immediately. Hence, we will move our main.ts code to another file called bootstrap.ts. And main.ts will import this bootstrap.ts file using the dynamic import. This will give time for the application to load firs the remoteEntry.js file and decide on shared libraries.

// main.ts
import('./bootstrap').catch((err) => console.error(err));

If you will do this, you will see the following error.

Error: Shared module is not available for eager consumption

Another solution is to load libraries eagerly in the shell and make them part of the main build. But that is not common and we can see that later in another article.

Step 5— Accessing Remote from Host

We are all set to access the remote application in our host app. We can use the Javascript dynamic import to access our application similar to other modules we load in an Angular application.

export const routes: Routes = [
  // This is local module
  {
    path: 'home',
    loadComponent: () =>
      import('./notes/notes.component').then((m) => m.NotesComponent),
  },
  // This is module from the remote app
  {
    path: 'customer',
    loadComponent: () =>
      import('remote/Customer').then((m) => m.CustomerComponent),
  },
];

The pattern for loading the remote module is

import(`${LOCAL_NAME_BY_HOST}/${REMOTE_EXPOSED_MODULE}`)

You can see how seamless and easy it is to access the remote module using the module federation. But wait, you might be seeing an error while importing.

Cannot find module 'remote/Customer' or its corresponding type declarations.

Typescript is not recognizing this module path and will give us the above error. To solve this issue, we usually declare a file at the root level called remotes.d.ts where we declare remote types. In our case, the file content would be

declare module 'remote/Customer';

And this is all for the setup to handle communication between the apps. Start both apps using “npm start” and open the browser on localhost:4200 URL or your application URL.

Step 6— Solving Multiple Angular Version Issues

And you will see some console errors :( Don’t worry that's on purpose to explain some concepts.

The error you will see in the console on the host is as follows.

As we are not sharing any libraries, @angular/core library is going to get loaded twice in the browser. And as we know, Angular does not work well with multiple versions loaded in the browser. To solve this issue, we need to share the core library between apps.

On both the host and the remote, add the following in the Webpack’s shared config that we left empty. Restart your apps and try again.

shared: {
  "@angular/core": {
    singleton: true,
    strictVersion: true,
    requiredVersion: "^17.1.0",
  },
},

and voila! Our apps will work after this. It is also advisable to share the other angular libraries as well. Hence, I am going to share the router and common as well to be fail-safe.

shared: {
  "@angular/core": {
    singleton: true,
    strictVersion: true,
    requiredVersion: "^17.1.0",
  },
  "@angular/common": {
    singleton: true,
    strictVersion: true,
    requiredVersion: "^17.1.0",
  },
  "@angular/router": {
    singleton: true,
    strictVersion: true,
    requiredVersion: "^17.1.0",
  },
}

Thank you for reading this long article. Hopefully, you will have learned something new today.

Please subscribe and stay tuned for the next articles.

Angular
Front End Development
Web Development
Modules
Programming
Recommended from ReadMedium