πŸ”— Angular Micro Frontends with Module Federation: How Closures Enable Dynamic Loading

Encapsulating Remote Modules, Lazy Loading Components, and Preventing Dependency Conflicts

Β·

3 min read

Let's deep dive into implementing Angular Micro Frontends (MFEs) with Module Federation, focusing on how closures help isolate modules and dynamically load remote components.


1️⃣ What is Module Federation in Angular?

Module Federation, introduced in Webpack 5, allows multiple independent Angular apps to communicate while remaining separately deployable.
βœ… Load Angular modules dynamically at runtime
βœ… Isolate dependencies using closures
βœ… Improve performance by sharing common libraries


2️⃣ How Closures Play a Role in Angular Micro Frontends

πŸ” Why Use Closures?

  • Each Micro Frontend encapsulates its dependencies in a closure, preventing global namespace conflicts.

  • Lazy loading ensures that a module is loaded only when needed, reducing initial bundle size.

  • Isolated runtime execution means each MFE runs inside its own function scope.

πŸ”Ή Example: How Module Federation Uses Closures

const mfe1 = (() => {
  let instance; // Closure keeps track of MFE instance

  return {
    init: function () {
      if (!instance) {
        instance = new AngularMFE(); // Closure ensures singleton instance
      }
      return instance;
    }
  };
})();
  • The closure hides internal state (instance), ensuring each MFE loads only once.

  • When mfe1.init() is called, it either returns an existing instance or creates a new one.


3️⃣ Implementing Angular Micro Frontends (Step-by-Step)

We’ll create: βœ… Shell App (Host Application) – Loads micro frontends dynamically.
βœ… Remote App (Micro Frontend) – Exposes Angular components to the shell.


πŸ“Œ Step 1: Install Webpack 5 Module Federation Plugin

Run:

ng add @angular-architects/module-federation

This installs Module Federation in your Angular app.


πŸ“Œ Step 2: Setup Remote Micro Frontend

πŸ”Ή Configure webpack.config.js for the Remote App

Modify projects/mfe1/webpack.config.js:

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

module.exports = {
  output: {
    publicPath: "http://localhost:4201/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "mfe1",
      filename: "remoteEntry.js",
      exposes: {
        "./Component": "./src/app/mfe1/mfe1.component.ts",
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"],
    }),
  ],
};

πŸ” Key Features of Closures Here

  • exposes: { "./Component": "./src/app/mfe1/mfe1.component.ts" }
    πŸ”Ή Encapsulates MFE inside a closure so that it's accessible only via API calls.

  • shared: ["@angular/core", "@angular/common"]
    πŸ”Ή Prevents duplicate dependencies, reducing bundle size.


πŸ“Œ Step 3: Setup Shell App (Host Application)

πŸ”Ή Configure webpack.config.js for the Shell

Modify projects/shell/webpack.config.js:

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

module.exports = {
  output: {
    publicPath: "http://localhost:4200/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        mfe1: "mfe1@http://localhost:4201/remoteEntry.js",
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"],
    }),
  ],
};

πŸ” Key Features of Closures Here


πŸ“Œ Step 4: Dynamically Load Remote Module in the Shell

Modify app-routing.module.ts in shell:

const routes: Routes = [
  { path: 'mfe1', loadChildren: () => import('mfe1/Component').then(m => m.Mfe1Component) }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

πŸ” What Happens Internally?

  1. When navigating to /mfe1, the shell calls import('mfe1/Component').

  2. Webpack fetches remoteEntry.js from mfe1.

  3. The exposes closure returns the Mfe1Component instance.

  4. The module is loaded into the host at runtime.


πŸš€ Running the Micro Frontend System

  1. Start the remote app:
ng serve mfe1 --port 4201
  1. Start the shell app:
ng serve shell --port 4200
  1. Navigate to http://localhost:4200/mfe1
    βœ…
    The Micro Frontend dynamically loads inside the shell! πŸŽ‰

πŸ”₯ Key Benefits of Using Closures in Angular Micro Frontends

βœ… Encapsulation: Each MFE is self-contained inside a closure, preventing conflicts.
βœ… Lazy Loading: Components load only when needed, improving performance.
βœ… Singleton Instances: Closures ensure that MFEs don’t reload unnecessarily.

Β