#angular #micro-frontends #module-federation #architecture

Day 27 — Micro-Frontends: Scalable Architecture with Module Federation

📘 Day 27 — Micro-Frontends: Scalable Architecture with Module Federation

Zero to Hero — Hands-on Angular Tutorial

Today you will learn:

  • ✔️ What are Micro-Frontends?
  • ✔️ Module Federation (Webpack 5)
  • ✔️ The Shell vs. Remote concept
  • ✔️ Setting up a Host App (Shell) and a Child App (Remote)
  • ✔️ Loading a Remote Micro-Frontend at runtime

Micro-Frontends allow you to split a massive app (Monolith) into smaller, independent apps that can be deployed separately but run together in the browser. 🤯


🟦 1. Why Micro-Frontends?

Imagine Amazon.com.

  • Header Team: Deploys the search bar.
  • Product Team: Deploys the item details.
  • Cart Team: Deploys the checkout.

If the Cart Team breaks their code, the Search Bar should still work. If the Product Team wants to upgrade to Angular v20, they shouldn’t force the Header Team to upgrade instantly.

Micro-Frontends enable:

  1. Independent Deployments.
  2. Independent Tech Stacks (Angular Shell loading React Child).
  3. Team Autonomy.

🟩 2. Module Federation (The Magic)

We use @angular-architects/module-federation to handle the complexity.

Scenario:

  1. Shell: The main app (Menu, Layout).
  2. MFE1: A “Profile” app (Remote).

Step 1: Create Projects (Nx or CLI)

# If using standard CLI
ng new shell
ng new mfe1

Step 2: Add Module Federation

ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe1  --port 4201 --type remote

This creates a webpack.config.js in both apps.


🟧 3. Configure the Remote (Child)

We want to expose a standardized Module or Component from mfe1 so shell can use it.

mfe1/webpack.config.js:

module.exports = withModuleFederationPlugin({
  name: 'mfe1',
  exposes: {
    // We expose THIS component to the world
    './Component': './src/app/app.component.ts', 
    // Or expose a whole Module with Routes
    './Module': './src/app/profile/profile.module.ts' 
  },
  shared: { 
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }) 
  },
});
  • exposes: The public API of this Micro-Frontend.
  • shared: Helps both apps share the same Angular instance (prevents loading Angular twice).

🟥 4. Configure the Shell (Parent)

The Shell needs to know where mfe1 lives.

shell/webpack.config.js:

module.exports = withModuleFederationPlugin({
  remotes: {
    // "mfe1" is the name defined in the remote config
    // "http://localhost:4201/remoteEntry.js" is the entry file generated by webpack
    "mfe1": "http://localhost:4201/remoteEntry.js", 
  },
  shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }) },
});

🟫 5. Loading the Remote Route

Now, in the Shell’s router, we lazy load the Remote just like a normal module!

shell/src/app/app.routes.ts:

import { Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';

export const routes: Routes = [
  {
    path: 'profile',
    loadChildren: () =>
      loadRemoteModule({
        type: 'manifest',
        remoteName: 'mfe1',
        exposedModule: './Module',
      }).then((m) => m.ProfileModule),
  },
  // Or load a standalone component
  {
    path: 'widget',
    loadComponent: () =>
      loadRemoteModule({
        type: 'module',
        remoteEntry: 'http://localhost:4201/remoteEntry.js',
        exposedModule: './Component',
      }).then((m) => m.AppComponent),
  }
];

🟦 6. Running it

  1. Start MFE1: ng serve mfe1 (Ports 4201).
  2. Start Shell: ng serve shell (Ports 4200).
  3. Open http://localhost:4200.
  4. Navigate to /profile.

Result: The Shell (4200) downloads the code from MFE1 (4201) on the fly and renders it. 🚀


🎉 End of Day 27 — What You Learned

Today you broke the monolith:

  • ✔️ Module Federation: The standard for sharing JS code at runtime.
  • ✔️ Host vs Remote: Organizing the architecture.
  • ✔️ Exopes/Remotes Config: Wiring up Webpack.
  • ✔️ Runtime Loading: loadRemoteModule in the Router.

🧪 Day 27 Challenge

“Micro-Dashboard”

  1. Create Shell app (4200).
  2. Create Stats app (4201).
  3. In Stats, create a standalone StatsCardComponent.
  4. Expose the component in Stats webpack config.
  5. Load the component in the Shell homepage using *ngComponentOutlet (or Routing).
  6. Verify that if you stop the Stats server, the Shell still works (but that part fails gracefully).