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


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
"mfe1@
http://localhost:4201/remoteEntry.js"
πΉ Calls the closure insidemfe1
to dynamically load its module at runtime.
π 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?
When navigating to
/mfe1
, the shell callsimport('mfe1/Component')
.Webpack fetches
remoteEntry.js
from mfe1.The
exposes
closure returns the Mfe1Component instance.The module is loaded into the host at runtime.
π Running the Micro Frontend System
- Start the remote app:
ng serve mfe1 --port 4201
- Start the shell app:
ng serve shell --port 4200
- 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.
Subscribe to my newsletter
Read articles from Rohit Bhat directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
