Skip to content

Angular MFE Module Federation Setup | Complete Configuration Guide

This guide walks you through configuring Webpack Module Federation for your Angular Micro Frontend applications, enabling them to share code and components at runtime.

Module Federation is a Webpack 5 feature that allows multiple separate builds to form a single application. It enables:

  • Runtime Code Sharing: Load modules from other applications at runtime
  • Independent Deployment: Each MFE can be deployed separately
  • Shared Dependencies: Avoid duplicating common libraries
  • Dynamic Imports: Load remote modules on-demand
  • Host: Application that consumes remote modules (your main shell app)
  • Remote: Application that exposes modules to be consumed
  • Federated Modules: Components/modules shared between applications
  • Shared Dependencies: Libraries shared to prevent duplication

🛠️ Step 1: Install Module Federation Dependencies

Section titled “🛠️ Step 1: Install Module Federation Dependencies”

1.1 Install Angular Architects Module Federation

Section titled “1.1 Install Angular Architects Module Federation”

For each application (host and all remotes), install the required packages:

Unix/macOS/Windows:

Terminal window
# Navigate to each project and run:
npm install @angular-architects/module-federation --save-dev
# Alternative: Install specific version
npm install @angular-architects/module-federation@^17.0.0 --save-dev

Unix/macOS/Windows:

Terminal window
# Additional webpack dependencies
npm install @angular-builders/custom-webpack --save-dev
npm install webpack --save-dev
npm install webpack-cli --save-dev

Let’s start by configuring the host application (host-banking-app):

Unix/macOS:

Terminal window
cd host-banking-app
# Add module federation schematic
ng add @angular-architects/module-federation --type host --port 4200

Windows:

Terminal window
Set-Location host-banking-app
# Add module federation schematic
ng add @angular-architects/module-federation --type host --port 4200

This command will:

  • Install necessary dependencies
  • Create webpack.config.js
  • Update angular.json
  • Modify project configuration

The generated webpack.config.js should look like this:

const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
mode: "development",
plugins: [
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"mfeLoanCalculator": "http://localhost:4201/remoteEntry.js",
"mfeUserDashboard": "http://localhost:4202/remoteEntry.js",
"mfeTransactionHistory": "http://localhost:4203/remoteEntry.js",
},
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common/http": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
},
}),
],
};

The angular.json should be updated to use custom webpack:

{
"projects": {
"host-banking-app": {
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.config.js"
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"configurations": {
"production": {
"buildTarget": "host-banking-app:build:production"
},
"development": {
"buildTarget": "host-banking-app:build:development",
"port": 4200
}
}
}
}
}
}
}

2.4 Create MFE Manifest (Dynamic Configuration)

Section titled “2.4 Create MFE Manifest (Dynamic Configuration)”

Create src/assets/mf.manifest.json for dynamic remote configuration:

{
"mfeLoanCalculator": "http://localhost:4201/remoteEntry.js",
"mfeUserDashboard": "http://localhost:4202/remoteEntry.js",
"mfeTransactionHistory": "http://localhost:4203/remoteEntry.js"
}

🎯 Step 3: Configure Remote Applications

Section titled “🎯 Step 3: Configure Remote Applications”

Now let’s configure each remote MFE:

Unix/macOS:

Terminal window
cd mfe-loan-calculator
# Add module federation as remote
ng add @angular-architects/module-federation --type remote --port 4201

Windows:

Terminal window
Set-Location mfe-loan-calculator
# Add module federation as remote
ng add @angular-architects/module-federation --type remote --port 4201

Update webpack.config.js in mfe-loan-calculator:

const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
mode: "development",
plugins: [
new ModuleFederationPlugin({
name: "mfeLoanCalculator",
filename: "remoteEntry.js",
exposes: {
"./LoanCalculatorModule": "./src/app/loan-calculator/loan-calculator.module.ts",
"./LoanCalculatorComponent": "./src/app/loan-calculator/loan-calculator.component.ts",
},
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common/http": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
},
}),
],
};

Create a dedicated module for the loan calculator:

Unix/macOS/Windows:

Terminal window
ng generate module loan-calculator --routing

loan-calculator.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LoanCalculatorRoutingModule } from './loan-calculator-routing.module';
import { LoanCalculatorComponent } from './loan-calculator.component';
@NgModule({
declarations: [
LoanCalculatorComponent
],
imports: [
CommonModule,
FormsModule,
LoanCalculatorRoutingModule
],
exports: [
LoanCalculatorComponent
]
})
export class LoanCalculatorModule { }

loan-calculator-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoanCalculatorComponent } from './loan-calculator.component';
const routes: Routes = [
{
path: '',
component: LoanCalculatorComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LoanCalculatorRoutingModule { }

Unix/macOS:

Terminal window
cd mfe-user-dashboard
# Add module federation
ng add @angular-architects/module-federation --type remote --port 4202

Windows:

Terminal window
Set-Location mfe-user-dashboard
# Add module federation
ng add @angular-architects/module-federation --type remote --port 4202

webpack.config.js for user dashboard:

const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
mode: "development",
plugins: [
new ModuleFederationPlugin({
name: "mfeUserDashboard",
filename: "remoteEntry.js",
exposes: {
"./UserDashboardModule": "./src/app/user-dashboard/user-dashboard.module.ts",
"./UserDashboardComponent": "./src/app/user-dashboard/user-dashboard.component.ts",
},
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common/http": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
},
}),
],
};

Create corresponding module files similar to the loan calculator.

Unix/macOS:

Terminal window
cd mfe-transaction-history
# Add module federation
ng add @angular-architects/module-federation --type remote --port 4203

Windows:

Terminal window
Set-Location mfe-transaction-history
# Add module federation
ng add @angular-architects/module-federation --type remote --port 4203

Follow the same pattern for webpack configuration and module creation.


🔗 Step 4: Configure Host to Load Remotes

Section titled “🔗 Step 4: Configure Host to Load Remotes”

Update app-routing.module.ts in the host application:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
const routes: Routes = [
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './UserDashboardModule'
}).then(m => m.UserDashboardModule)
},
{
path: 'loan-calculator',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './LoanCalculatorModule'
}).then(m => m.LoanCalculatorModule)
},
{
path: 'transactions',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4203/remoteEntry.js',
exposedModule: './TransactionHistoryModule'
}).then(m => m.TransactionHistoryModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Update app.component.html in the host application:

<div class="app-container">
<header class="app-header">
<nav class="navbar">
<div class="nav-brand">
<h1>🏦 Banking Platform</h1>
</div>
<ul class="nav-links">
<li><a routerLink="/dashboard" routerLinkActive="active">Dashboard</a></li>
<li><a routerLink="/loan-calculator" routerLinkActive="active">Loan Calculator</a></li>
<li><a routerLink="/transactions" routerLinkActive="active">Transactions</a></li>
</ul>
</nav>
</header>
<main class="app-content">
<router-outlet></router-outlet>
</main>
<footer class="app-footer">
<p>&copy; 2024 Banking Platform - Powered by Micro Frontends</p>
</footer>
</div>

app.component.scss:

.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.app-header {
background: #2c3e50;
color: white;
padding: 0;
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
max-width: 1200px;
margin: 0 auto;
.nav-brand h1 {
margin: 0;
font-size: 1.5rem;
}
.nav-links {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 2rem;
li a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.3s;
&:hover, &.active {
background-color: #34495e;
}
}
}
}
}
.app-content {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
.app-footer {
background: #34495e;
color: white;
text-align: center;
padding: 1rem;
margin-top: auto;
}
@media (max-width: 768px) {
.navbar {
flex-direction: column;
gap: 1rem;
.nav-links {
gap: 1rem;
}
}
.app-content {
padding: 1rem;
}
}

🔧 Step 5: Dynamic Configuration Service

Section titled “🔧 Step 5: Dynamic Configuration Service”

Create a service to dynamically load MFE configurations:

Unix/macOS:

Terminal window
cd host-banking-app
ng generate service services/mfe-config

Windows:

Terminal window
Set-Location host-banking-app
ng generate service services/mfe-config

mfe-config.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface MfeConfig {
[key: string]: string;
}
@Injectable({
providedIn: 'root'
})
export class MfeConfigService {
constructor(private http: HttpClient) { }
loadMfeConfig(): Observable<MfeConfig> {
return this.http.get<MfeConfig>('/assets/mf.manifest.json');
}
async loadRemoteModule(remoteName: string, exposedModule: string) {
const config = await this.loadMfeConfig().toPromise();
const remoteEntry = config![remoteName];
if (!remoteEntry) {
throw new Error(`Remote ${remoteName} not found in configuration`);
}
return import(/* webpackIgnore: true */ remoteEntry).then(() => {
return (window as any)[remoteName];
});
}
}

Create a utility for dynamic route loading:

utils/dynamic-route-loader.ts
import { loadRemoteModule } from '@angular-architects/module-federation';
export interface RemoteModuleConfig {
remoteEntry: string;
exposedModule: string;
moduleName: string;
}
export function loadDynamicRemoteModule(config: RemoteModuleConfig) {
return () => loadRemoteModule({
type: 'module',
remoteEntry: config.remoteEntry,
exposedModule: config.exposedModule
}).then(m => m[config.moduleName]);
}

🧪 Step 6: Testing Module Federation Setup

Section titled “🧪 Step 6: Testing Module Federation Setup”

Create comprehensive test scripts for your platform:

Unix/macOS (test-mfe.sh):

#!/bin/bash
echo "🚀 Starting all MFE applications for testing..."
# Function to start an application in background
start_app() {
local app_name=$1
local port=$2
echo "Starting $app_name on port $port..."
cd $app_name
npm start > ../logs/$app_name.log 2>&1 &
local pid=$!
echo $pid > ../logs/$app_name.pid
cd ..
echo "$app_name started with PID $pid"
}
# Create logs directory
mkdir -p logs
# Start all applications
start_app "mfe-loan-calculator" 4201
start_app "mfe-user-dashboard" 4202
start_app "mfe-transaction-history" 4203
start_app "host-banking-app" 4200
echo ""
echo "🎉 All applications started!"
echo ""
echo "📱 Application URLs:"
echo " Host App: http://localhost:4200"
echo " Loan Calculator: http://localhost:4201"
echo " User Dashboard: http://localhost:4202"
echo " Transaction History: http://localhost:4203"
echo ""
echo "⏳ Waiting for applications to start..."
sleep 10
echo "🧪 Testing remoteEntry.js files..."
curl -s -o /dev/null -w "%{http_code}" http://localhost:4201/remoteEntry.js
curl -s -o /dev/null -w "%{http_code}" http://localhost:4202/remoteEntry.js
curl -s -o /dev/null -w "%{http_code}" http://localhost:4203/remoteEntry.js
echo ""
echo "✅ Module Federation setup test complete!"
echo "Press any key to stop all applications..."
read -n 1
# Stop all applications
for pidfile in logs/*.pid; do
if [ -f "$pidfile" ]; then
pid=$(cat "$pidfile")
kill $pid 2>/dev/null
rm "$pidfile"
fi
done
echo "🛑 All applications stopped."

Windows (test-mfe.ps1):

Terminal window
Write-Host "🚀 Starting all MFE applications for testing..." -ForegroundColor Green
# Function to start an application in background
function Start-App {
param(
[string]$AppName,
[int]$Port
)
Write-Host "Starting $AppName on port $Port..." -ForegroundColor Yellow
Set-Location $AppName
$process = Start-Process npm -ArgumentList "start" -PassThru -WindowStyle Hidden -RedirectStandardOutput "..\logs\$AppName.log" -RedirectStandardError "..\logs\$AppName.log"
$process.Id | Out-File -FilePath "..\logs\$AppName.pid"
Set-Location ..
Write-Host "$AppName started with PID $($process.Id)" -ForegroundColor Green
return $process
}
# Create logs directory
New-Item -Path "logs" -ItemType Directory -Force | Out-Null
# Start all applications
$loanCalcProcess = Start-App "mfe-loan-calculator" 4201
$dashboardProcess = Start-App "mfe-user-dashboard" 4202
$transactionProcess = Start-App "mfe-transaction-history" 4203
$hostProcess = Start-App "host-banking-app" 4200
Write-Host ""
Write-Host "🎉 All applications started!" -ForegroundColor Green
Write-Host ""
Write-Host "📱 Application URLs:" -ForegroundColor Cyan
Write-Host " Host App: http://localhost:4200"
Write-Host " Loan Calculator: http://localhost:4201"
Write-Host " User Dashboard: http://localhost:4202"
Write-Host " Transaction History: http://localhost:4203"
Write-Host ""
Write-Host "⏳ Waiting for applications to start..." -ForegroundColor Yellow
Start-Sleep -Seconds 10
Write-Host "🧪 Testing remoteEntry.js files..." -ForegroundColor Blue
try { $response1 = Invoke-WebRequest -Uri "http://localhost:4201/remoteEntry.js" -UseBasicParsing; Write-Host "Loan Calculator: $($response1.StatusCode)" } catch { Write-Host "Loan Calculator: Error" }
try { $response2 = Invoke-WebRequest -Uri "http://localhost:4202/remoteEntry.js" -UseBasicParsing; Write-Host "User Dashboard: $($response2.StatusCode)" } catch { Write-Host "User Dashboard: Error" }
try { $response3 = Invoke-WebRequest -Uri "http://localhost:4203/remoteEntry.js" -UseBasicParsing; Write-Host "Transaction History: $($response3.StatusCode)" } catch { Write-Host "Transaction History: Error" }
Write-Host ""
Write-Host "✅ Module Federation setup test complete!" -ForegroundColor Green
Read-Host "Press Enter to stop all applications"
# Stop all applications
Stop-Process -Id $loanCalcProcess.Id -Force -ErrorAction SilentlyContinue
Stop-Process -Id $dashboardProcess.Id -Force -ErrorAction SilentlyContinue
Stop-Process -Id $transactionProcess.Id -Force -ErrorAction SilentlyContinue
Stop-Process -Id $hostProcess.Id -Force -ErrorAction SilentlyContinue
Write-Host "🛑 All applications stopped." -ForegroundColor Yellow
  1. Start Applications:

    Unix/macOS:

    Terminal window
    chmod +x test-mfe.sh
    ./test-mfe.sh

    Windows:

    Terminal window
    # Enable script execution if needed
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
    # Run the test script
    .\test-mfe.ps1
  2. Verify Remote Entries:

  3. Test Host Application:

Issue 1: RemoteEntry.js not found

Unix/macOS:

Terminal window
# Check if remote is running
curl http://localhost:4201/remoteEntry.js
# Verify webpack configuration
npm run build -- --stats-error-details

Windows:

Terminal window
# Check if remote is running
Invoke-WebRequest -Uri "http://localhost:4201/remoteEntry.js" -UseBasicParsing
# Verify webpack configuration
npm run build -- --stats-error-details

Issue 2: Shared dependency conflicts

Terminal window
# Check shared dependencies versions
npm ls @angular/core
npm ls @angular/common
# Update webpack shared config if needed

Issue 3: CORS issues in development

Terminal window
# Add to angular.json serve options:
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"headers": {
"Access-Control-Allow-Origin": "*"
}
}
}

Create webpack.prod.config.js for production builds:

const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
mode: "production",
plugins: [
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"mfeLoanCalculator": "https://loan-calculator.yourdomain.com/remoteEntry.js",
"mfeUserDashboard": "https://user-dashboard.yourdomain.com/remoteEntry.js",
"mfeTransactionHistory": "https://transaction-history.yourdomain.com/remoteEntry.js",
},
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "auto"
},
},
}),
],
};

Update mf.manifest.json structure:

{
"development": {
"mfeLoanCalculator": "http://localhost:4201/remoteEntry.js",
"mfeUserDashboard": "http://localhost:4202/remoteEntry.js",
"mfeTransactionHistory": "http://localhost:4203/remoteEntry.js"
},
"production": {
"mfeLoanCalculator": "https://loan-calculator.yourdomain.com/remoteEntry.js",
"mfeUserDashboard": "https://user-dashboard.yourdomain.com/remoteEntry.js",
"mfeTransactionHistory": "https://transaction-history.yourdomain.com/remoteEntry.js"
}
}

  • Module Federation installed in all applications
  • Webpack configs created for host and remotes
  • Remote modules properly exposed
  • Host application configured to consume remotes
  • Navigation working between MFEs
  • Shared dependencies configured correctly
  • RemoteEntry.js files accessible
  • Dynamic routing implemented
  • Production configuration prepared
  • Testing script working
Project Structure After Module Federation Setup:
├── host-banking-app/
│ ├── webpack.config.js
│ ├── webpack.prod.config.js
│ ├── src/assets/mf.manifest.json
│ └── updated angular.json
├── mfe-loan-calculator/
│ ├── webpack.config.js
│ ├── loan-calculator.module.ts
│ └── updated angular.json
├── mfe-user-dashboard/
│ ├── webpack.config.js
│ ├── user-dashboard.module.ts
│ └── updated angular.json
└── mfe-transaction-history/
├── webpack.config.js
├── transaction-history.module.ts
└── updated angular.json

Your Module Federation setup is complete! Next steps:

  1. Module Federation Setup Complete
  2. ➡️ Continue to: Host App Configuration - Enhance the host application
  3. 📚 Alternative: Remote Configuration - Advanced remote setup

Your Angular MFEs can now communicate and share code at runtime! 🎉