Implementing Micro Frontends in Angular: Step-By-Step Guide
This guide explains how to implement micro frontends in Angular applications with scalability and maintainability in mind.
Join the DZone community and get the full member experience.
Join For FreeMicro frontends are architectural styles in which independently deliverable frontend applications form a unified whole. This method enables teams to work independently on different parts of an application for scalability and easier maintenance. This guide will demonstrate how to implement micro frontends in Angular applications, the benefits and challenges, and the steps with code samples.
Benefits of Micro Frontends
- Scalability: Teams can develop, test, and deploy features independently, scaling the development process.
- Autonomous teams: Different teams can work on separate micro frontends, using their preferred tools and frameworks.
- Maintainability: Smaller codebases are easier to maintain, understand, and refactor.
- Incremental upgrades: Micro frontends can be updated incrementally without affecting the entire application.
Challenges of Micro Frontends
- Complexity: Increased complexity in managing multiple repositories and coordinating deployments.
- Performance: Potential performance overhead due to multiple independent bundles.
- Shared state management: Managing shared state across micro frontends can be challenging.
- Consistency: Ensuring a consistent look and feel across different micro frontends requires careful planning.
When more than one team works on various areas of an application, differences in design and user experience usually creep in. To mitigate this, a solid design system and common component library should be established across all teams. This includes defining a set of UI components, styles, and guidelines that are uniform across the application. It also requires regular communication and collaboration between teams to align on design decisions and consistency.
Setting up Micro Frontends With Angular
Step 1: Creating the Shell Application
The shell application serves as the container for the micro frontends. It handles routing and provides a common layout.
1. Create a New Angular Application
ng new shell-app
cd shell-app
2. Set up Angular Router
In app-routing.module.ts
, define routes for the micro frontends.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: 'micro-frontend1', loadChildren: () => import('micro-frontend1/Module').then(m => m.MicroFrontend1Module) },
{ path: 'micro-frontend2', loadChildren: () => import('micro-frontend2/Module').then(m => m.MicroFrontend2Module) },
{ path: '', redirectTo: '/micro-frontend1', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Step 2: Creating Micro Frontend Applications
Each micro frontend is a separate Angular application that can be developed and deployed independently.
1. Create New Angular Applications
ng new micro-frontend1
ng new micro-frontend2
2. Configure Webpack Module Federation Plugin
To enable dynamic loading of micro frontends, we use the Webpack Module Federation Plugin. Add the following configuration to webpack.config.js
in each micro frontend project.
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
output: {
publicPath: "http://localhost:4201/",
uniqueName: "micro-frontend1"
},
plugins: [
new ModuleFederationPlugin({
name: "micro-frontend1",
filename: "remoteEntry.js",
exposes: {
'./Module': './src/app/app.module.ts',
},
shared: {
"@angular/core": { singleton: true, strictVersion: true },
"@angular/common": { singleton: true, strictVersion: true },
"@angular/router": { singleton: true, strictVersion: true },
}
})
],
};
Repeat similar steps for micro-frontend2
with appropriate adjustments.
3. Adjust angular.json
for Custom Webpack Configuration
Modify angular.json
to use the custom Webpack configuration.
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.config.js"
}
}
}
}
4. Expose Components and Modules
Expose the necessary components and modules in each micro frontend. For example, in micro-frontend1
:
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Step 3: Integrating Micro Frontends Into the Shell Application
1. Configure Webpack Module Federation in Shell
Similar to the micro frontends, configure the Webpack Module Federation Plugin in the shell application.
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
output: {
publicPath: "http://localhost:4200/",
uniqueName: "shell-app"
},
plugins: [
new ModuleFederationPlugin({
remotes: {
"micro-frontend1": "micro-frontend1@http://localhost:4201/remoteEntry.js",
"micro-frontend2": "micro-frontend2@http://localhost:4202/remoteEntry.js"
},
shared: {
"@angular/core": { singleton: true, strictVersion: true },
"@angular/common": { singleton: true, strictVersion: true },
"@angular/router": { singleton: true, strictVersion: true },
}
})
],
};
2. Configure Routes in the Shell Application
Ensure the routes in the shell application are correctly configured to load the micro frontends.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: 'micro-frontend1', loadChildren: () => import('micro-frontend1/Module').then(m => m.MicroFrontend1Module) },
{ path: 'micro-frontend2', loadChildren: () => import('micro-frontend2/Module').then(m => m.MicroFrontend2Module) },
{ path: '', redirectTo: '/micro-frontend1', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
3. Run the Applications
Ensure all applications (shell and micro frontends) are running.
ng serve --project shell-app
ng serve --project micro-frontend1
ng serve --project micro-frontend2
Visit http://localhost:4200
to see the shell application, which should load the micro frontends as configured.
Step 4: Handling Shared State and Communication
Managing shared state and communication between micro frontends is crucial for a cohesive user experience.
1. Using a Shared Library
Create a shared library that can be used by all micro frontends to manage the state and provide common services.
ng generate library shared
In the shared library, define common services and state management logic. For instance:
// shared/src/lib/state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class StateService {
private stateSubject = new BehaviorSubject<any>({});
state$ = this.stateSubject.asObservable();
setState(newState: any) {
this.stateSubject.next(newState);
}
}
2. Using the Shared Library in Micro Frontends
Import and use the shared library in each micro frontend to maintain a consistent state and facilitate communication.
// micro-frontend1/src/app/app.component.ts
import { Component } from '@angular/core';
import { StateService } from 'shared';
@Component({
selector: 'app-root',
template: `<h1>Micro Frontend 1</h1>`
})
export class AppComponent {
constructor(private stateService: StateService) {
this.stateService.setState({ key: 'value' });
}
}
Repeat similar steps for other micro frontends to ensure they can communicate via the shared state service.
Conclusion
Implementing micro frontends with Angular requires configuring a shell application to host multiple independent micro frontends, and configuring Webpack Module Federation for dynamic loading and shared state and communication. This architecture fosters scalability, maintainability, and autonomous team operations. However, it also brings complexity and performance considerations to be managed carefully.
Follow this guide to get started developing micro frontends in Angular that leverage the benefits and solve the challenges you face.
Opinions expressed by DZone contributors are their own.
Comments