Implementing Micro-Frontends in Angular with Module Federation
Are you an Angular developer overwhelmed by managing a monolithic application? Ready to embrace the power of micro frontends and module federation but could not find right resource to start with? This guide is your step-by-step easy to follow blueprint to seamlessly transition from monolith to micro frontends and unlock a new level of scalability and flexibility for your web applications
Angular is a widely used framework to develop modern, high performance web applications for its strong eco system and power packed features like dependency injection, component-based architecture, two-way data binding, modular structure and built in CLI tools. However, angular applications are traditionally built as monoliths which causes maintenance issues as the code base grows over time. This makes development and build time slower since every change requires rebuilding the entire application resulting in slower deployment cycles. Additionally shared libraries used across a growing codebase require proper versioning, as even slight incompatibilities between them can cause functionality to break.
This is where splitting application features into different micro front ends using becomes invaluable to improve overall development and application performance. Here are some key advantages of using MFE based architecture:
- Applications can be split into independently deployable MFEs, allowing teams to work on separate modules without affecting the other
- New features can be added seamlessly as only modified micro-frontend needs to be rebuilt and redeployed, reducing build and deployment time
- Independent micro frontends enable loose coupling and better reusability of components across the application
- Each micro frontend can have its own version of libraries or even a different development framework, allowing gradual upgrades and better flexibility
- Bugs are isolated to specific micro frontends, making debugging and maintenance easier
So, how to start with module federation to build MFE based angular application? below are the elaborate steps to start with the development of sample product managing front end. This application will include one host app to display a list of products and two MFEs for product details and product addition functionality, respectively.
Steps
- First create a mono workspace without generating default application, to have common configuration, shared libraries and package dependencies for different MFEs that will be part of it
ng new product-management - create-application=false
- Install module federation
cd product-management
npm install @angular-architects/module-federation --save-dev
- Generate one host application (product-list) and two remote applications (product-details and create-product) for this product management application
ng generate application product-list --routing --style=css
ng generate application product-details --routing --style=css
ng generate application create-product --routing --style=css
- To define the Product model and Service that will communicate with the backend APIs and will be imported by all the MFEs, create a shared library. Here we can define reusable components ex. component to view the product details or component that defines template to use in create/update product MFE. These custom components can be declared and exported using product.module.ts and public-api.ts of the shared library
ng generate library shared-product
- Configure and add product-list application as host application which will dynamically load remote MFEs at runtime. This host application will run on 4200 port
ng add @angular-architects/module-federation --project=product-list --type=host --port=4200 --stack=module-federation-webpack
- Add product-details and create-product as remote applications which will export their respective functionalities using module federation. Assign each of them different ports.
ng add @angular-architects/module-federation --project=product-details --type=remote --port=4201 --stack=module-federation-webpack
ng add @angular-architects/module-federation --project=create-product --type=remote --port=4202 --stack=module-federation-webpack
Above steps will add all the necessary MFEs required for this application, configure them with module federation and add all the configuration files required for the communication between the MFEs. webpack.config.ts will be generated for each application and defines the entry point for the remote MFEs. Now coming to code configuration…
- After adding necessary components for all three applications, add below configuration to webpack.config.ts of create-product application. This configuration will setup this application as remote MFE and exposes its AppModule through remoteEntry.js, where module’s metadata is defined. Host application loads this file dynamically to fetch the exposed AppModule.
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
output: {
uniqueName: "createProduct",
publicPath: "auto",
scriptType: "text/javascript"
},
optimization: {
runtimeChunk: false
},
plugins: [
new ModuleFederationPlugin({
name: 'createProduct', // Name of remote app, which host will uniquely identify
filename: 'remoteEntry.js', // Name of the remote entry file that will be accessible to host
exposes: {
'./AppModule': './projects/create-product/src/app/app.module.ts', // Exposes AppModule
},
shared: {
// Shared libraries here if necessary
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true},
'@angular/router': { singleton: true, strictVersion: true},
},
}),
],
};
- Since remote MFEs act as feature modules and loaded dynamically within host application, they should use RouterModule.forChild() to integrate their routes without disrupting the host’s router configuration. So, add below configuration in app-routing.module.ts.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
const routes: Routes = [
{path: '', component: AppComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
- Similarly add below configuration to webpack.config.ts of product-details application and change app-routing.module.ts to use RouterModule.forChild()
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
output: {
uniqueName: "productDetails",
publicPath: "auto",
scriptType: "text/javascript"
},
optimization: {
runtimeChunk: false
},
plugins: [
new ModuleFederationPlugin({
name: 'productDetails',
filename: 'remoteEntry.js',
exposes: {
'./AppModule': './projects/product-details/src/app/app.module.ts', // Expose AppModule
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true},
'@angular/router': { singleton: true, strictVersion: true},
},
}),
],
};
- Now we have remote applications/MFEs ready to serve, add below configuration to webpack.config.ts of product-list, which will use these remote front ends to load them whenever needed
const { withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'productList', // unique name of host application
remotes: {
productDetails: 'productDetails@http://localhost:4201/remoteEntry.js', //remote mfe address to dynamically load the component
createProduct: 'createProduct@http://localhost:4202/remoteEntry.js'
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true },
'@angular/router': { singleton: true, strictVersion: true },
},
});
- Finally, to use these remote modules, define routes in app-routing.module.ts of product-list application
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
import { AppComponent } from './app.component';
const routes: Routes = [
{ path: 'products', component: AppComponent },
{ path: '', redirectTo: 'products', pathMatch: 'full' },
{
path: 'product-details/:name',
loadChildren: () =>
loadRemoteModule({
remoteName: 'productDetails', // Remote app's name
remoteEntry: 'http://localhost:4202/remoteEntry.js', // Remote entry file
exposedModule: './AppModule', // Module exposed from the remote app
}).then((m) => {
return m.AppModule;
}),
},
{
path: 'create-product',
loadChildren: () =>
loadRemoteModule({
remoteName: 'createproduct',
remoteEntry: 'http://localhost:4203/remoteEntry.js',
exposedModule: './AppModule',
}).then((m) => {
return m.AppModule;
}),
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
And that’s it. Now you can run all three applications simultaneously to start using MFE based product management application!!
Summary
Modern web applications are increasingly adapting to use micro frontend-based architecture as it enables the development of independent, self-contained front-end modules that can be deployed and updated separately, reducing dependencies between teams and improving collaboration. This guide outlines minimum steps to build MFE based angular web application, helping you in basic setup to start developing on top of it.