Skip to content

Angular MFE Documentation & Advanced Concepts | Complete Guide

This final guide covers advanced Micro Frontend concepts, best practices, and strategies for creating comprehensive documentation for your Angular MFE platform.

  1. Technical Documentation: API references, architecture diagrams
  2. User Guides: End-user tutorials and workflows
  3. Developer Documentation: Setup guides, contribution guidelines
  4. Architecture Documentation: System design and patterns
  5. Deployment Guides: Infrastructure and deployment processes
Documentation Architecture:
├── README.md (Overview)
├── docs/
│ ├── architecture/
│ │ ├── overview.md
│ │ ├── mfe-patterns.md
│ │ └── security.md
│ ├── development/
│ │ ├── getting-started.md
│ │ ├── local-setup.md
│ │ └── testing.md
│ ├── deployment/
│ │ ├── azure-setup.md
│ │ └── ci-cd.md
│ ├── api/
│ │ └── mfe-interfaces.md
│ └── troubleshooting/
│ └── common-issues.md
└── examples/
├── loan-calculator/
├── user-dashboard/
└── transaction-history/

shared-event-bus.service.ts:

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
export interface MfeEvent {
type: string;
source: string;
payload: any;
timestamp: Date;
}
@Injectable({
providedIn: 'root'
})
export class SharedEventBusService {
private eventSubject = new Subject<MfeEvent>();
// Emit event to all MFEs
emit(type: string, payload: any, source: string): void {
const event: MfeEvent = {
type,
source,
payload,
timestamp: new Date()
};
this.eventSubject.next(event);
}
// Listen for specific event types
on(eventType: string): Observable<MfeEvent> {
return this.eventSubject.asObservable().pipe(
filter(event => event.type === eventType)
);
}
// Listen for events from specific MFE
fromSource(source: string): Observable<MfeEvent> {
return this.eventSubject.asObservable().pipe(
filter(event => event.source === source)
);
}
}

shared-state.service.ts:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
export interface SharedState {
user: any;
theme: 'light' | 'dark';
language: string;
notifications: any[];
}
@Injectable({
providedIn: 'root'
})
export class SharedStateService {
private stateSubject = new BehaviorSubject<SharedState>({
user: null,
theme: 'light',
language: 'en',
notifications: []
});
public state$ = this.stateSubject.asObservable();
updateUser(user: any): void {
const currentState = this.stateSubject.value;
this.stateSubject.next({
...currentState,
user
});
}
updateTheme(theme: 'light' | 'dark'): void {
const currentState = this.stateSubject.value;
this.stateSubject.next({
...currentState,
theme
});
// Apply theme globally
document.documentElement.setAttribute('data-theme', theme);
}
addNotification(notification: any): void {
const currentState = this.stateSubject.value;
this.stateSubject.next({
...currentState,
notifications: [...currentState.notifications, notification]
});
}
getState(): SharedState {
return this.stateSubject.value;
}
}

dynamic-mfe-loader.service.ts:

import { Injectable, Injector, ComponentRef, ViewContainerRef } from '@angular/core';
import { loadRemoteModule } from '@angular-architects/module-federation';
export interface MfeConfig {
name: string;
remoteEntry: string;
exposedModule: string;
componentName: string;
displayName: string;
version: string;
permissions: string[];
}
@Injectable({
providedIn: 'root'
})
export class DynamicMfeLoaderService {
private loadedMfes = new Map<string, any>();
private mfeConfigs = new Map<string, MfeConfig>();
constructor(private injector: Injector) {}
async registerMfe(config: MfeConfig): Promise<void> {
this.mfeConfigs.set(config.name, config);
try {
const module = await loadRemoteModule({
type: 'module',
remoteEntry: config.remoteEntry,
exposedModule: config.exposedModule
});
this.loadedMfes.set(config.name, module);
console.log(`✅ MFE ${config.name} registered successfully`);
} catch (error) {
console.error(`❌ Failed to register MFE ${config.name}:`, error);
}
}
async loadComponent(
mfeName: string,
container: ViewContainerRef
): Promise<ComponentRef<any> | null> {
const module = this.loadedMfes.get(mfeName);
const config = this.mfeConfigs.get(mfeName);
if (!module || !config) {
console.error(`MFE ${mfeName} not found or not registered`);
return null;
}
try {
const component = module[config.componentName];
const componentRef = container.createComponent(component);
return componentRef;
} catch (error) {
console.error(`Failed to load component from MFE ${mfeName}:`, error);
return null;
}
}
getMfeConfig(name: string): MfeConfig | undefined {
return this.mfeConfigs.get(name);
}
getAvailableMfes(): MfeConfig[] {
return Array.from(this.mfeConfigs.values());
}
}

mfe-error-boundary.component.ts:

import { Component, Input, OnInit, ErrorHandler, ViewChild, ViewContainerRef } from '@angular/core';
import { DynamicMfeLoaderService } from '../services/dynamic-mfe-loader.service';
@Component({
selector: 'app-mfe-error-boundary',
template: `
<div class="mfe-container">
<div *ngIf="!hasError" #mfeContainer></div>
<div *ngIf="hasError" class="mfe-error-fallback">
<div class="error-content">
<h3>⚠️ Unable to Load {{ mfeName }}</h3>
<p>{{ errorMessage }}</p>
<div class="error-actions">
<button class="btn btn-primary" (click)="retry()">
🔄 Retry
</button>
<button class="btn btn-secondary" (click)="showFallback()">
📄 Show Alternative
</button>
</div>
</div>
</div>
<div *ngIf="showingFallback" class="mfe-fallback">
<ng-content select="[slot=fallback]"></ng-content>
</div>
</div>
`,
styles: [`
.mfe-error-fallback {
padding: 2rem;
text-align: center;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
margin: 1rem 0;
}
.error-content h3 {
color: #dc3545;
margin-bottom: 1rem;
}
.error-actions {
margin-top: 1rem;
display: flex;
gap: 1rem;
justify-content: center;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
`]
})
export class MfeErrorBoundaryComponent implements OnInit {
@Input() mfeName!: string;
@Input() maxRetries = 3;
@ViewChild('mfeContainer', { read: ViewContainerRef }) container!: ViewContainerRef;
hasError = false;
showingFallback = false;
errorMessage = '';
retryCount = 0;
constructor(
private mfeLoader: DynamicMfeLoaderService,
private errorHandler: ErrorHandler
) {}
async ngOnInit() {
await this.loadMfe();
}
private async loadMfe(): Promise<void> {
try {
this.hasError = false;
this.showingFallback = false;
const componentRef = await this.mfeLoader.loadComponent(
this.mfeName,
this.container
);
if (!componentRef) {
throw new Error(`Failed to load MFE: ${this.mfeName}`);
}
} catch (error) {
this.handleError(error);
}
}
private handleError(error: any): void {
this.hasError = true;
this.errorMessage = error.message || 'Unknown error occurred';
console.error(`MFE ${this.mfeName} failed to load:`, error);
this.errorHandler.handleError(error);
}
async retry(): Promise<void> {
if (this.retryCount < this.maxRetries) {
this.retryCount++;
await this.loadMfe();
} else {
this.errorMessage = `Maximum retry attempts (${this.maxRetries}) exceeded`;
}
}
showFallback(): void {
this.showingFallback = true;
this.hasError = false;
}
}

security.service.ts:

import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SecurityService {
private trustedDomains: string[] = [
'https://banking.yourdomain.com',
'https://loan-calculator.yourdomain.com',
'https://user-dashboard.yourdomain.com',
'https://transactions.yourdomain.com'
];
validateMfeSource(url: string): boolean {
try {
const urlObj = new URL(url);
return this.trustedDomains.some(domain =>
urlObj.origin === new URL(domain).origin
);
} catch {
return false;
}
}
sanitizeData(data: any): any {
// Implement data sanitization logic
if (typeof data === 'string') {
return this.sanitizeString(data);
}
if (Array.isArray(data)) {
return data.map(item => this.sanitizeData(item));
}
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const [key, value] of Object.entries(data)) {
sanitized[this.sanitizeString(key)] = this.sanitizeData(value);
}
return sanitized;
}
return data;
}
private sanitizeString(input: string): string {
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '');
}
}

mfe-auth.guard.ts:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class MfeAuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
const requiredPermissions = route.data['permissions'] as string[];
const mfeName = route.data['mfeName'] as string;
if (!this.authService.isAuthenticated) {
console.warn(`Access denied to MFE ${mfeName}: User not authenticated`);
return false;
}
if (requiredPermissions && requiredPermissions.length > 0) {
const hasPermission = this.authService.hasPermissions(requiredPermissions);
if (!hasPermission) {
console.warn(`Access denied to MFE ${mfeName}: Insufficient permissions`);
return false;
}
}
return true;
}
}

smart-preload.strategy.ts:

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class SmartPreloadStrategy implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
const connection = (navigator as any).connection;
// Don't preload on slow connections
if (connection && connection.effectiveType === '2g') {
return of(null);
}
// Preload after a delay for non-critical routes
const delay = route.data?.['preloadDelay'] || 2000;
const priority = route.data?.['priority'] || 'low';
if (priority === 'high') {
return fn();
}
return timer(delay).pipe(
switchMap(() => fn())
);
}
}

bundle-analyzer.js:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
}
}
};

# 🏦 Banking Platform - Micro Frontend Architecture
> Modern, scalable banking platform built with Angular Micro Frontends
[![Build Status](https://github.com/yourusername/banking-platform/workflows/CI/badge.svg)](https://github.com/yourusername/banking-platform/actions)
[![Azure Static Web Apps](https://img.shields.io/badge/Azure-Static%20Web%20Apps-blue)](https://banking.yourdomain.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## 🚀 Quick Start
```bash
# Clone and start all MFEs
git clone https://github.com/yourusername/banking-platform.git
cd banking-platform
./start-all-dev.sh

Visit http://localhost:4200 to see the application.

This project demonstrates a production-ready Micro Frontend architecture with:

  • Host Application: Main shell and navigation
  • Loan Calculator MFE: Mortgage and loan calculations
  • User Dashboard MFE: Account overview and management
  • Transaction History MFE: Financial transaction tracking
TechnologyPurposeVersion
AngularFrontend Framework17+
Module FederationRuntime IntegrationWebpack 5
Azure Static Web AppsHosting & DeploymentLatest
TypeScriptType Safety5.0+
SCSSStylingLatest
banking-platform/
├── host-banking-app/ # Main shell application
├── mfe-loan-calculator/ # Loan calculator MFE
├── mfe-user-dashboard/ # User dashboard MFE
├── mfe-transaction-history/ # Transaction history MFE
├── docs/ # Documentation
├── scripts/ # Development scripts
└── .github/workflows/ # CI/CD pipelines
  • Node.js 18+
  • Angular CLI 17+
  • Git
  1. Start all applications:

    Terminal window
    npm run start:all
  2. Start individual MFE:

    Terminal window
    cd mfe-loan-calculator
    npm start
  3. Run tests:

    Terminal window
    npm run test:all
Terminal window
npm run build:all

This project is configured for Azure Static Web Apps with GitHub Actions:

  1. Automatic deployment on push to main branch
  2. Preview deployments for pull requests
  3. Custom domains with SSL certificates
  4. Global CDN distribution

See docs/deployment.md (in your own project’s docs folder) for detailed instructions.

Current performance metrics:

  • First Load: < 2 seconds
  • MFE Load Time: < 1 second
  • Bundle Size: < 500KB total
  • Lighthouse Score: 95+
  • Content Security Policy (CSP) implemented
  • HTTPS enforced
  • Authentication with Azure AD B2C
  • Input sanitization and validation
  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Commit changes: git commit -m 'Add amazing feature'
  4. Push to branch: git push origin feature/amazing-feature
  5. Open a Pull Request

See CONTRIBUTING.md (in your own project root) for detailed guidelines.

A well-documented MFE project typically has a docs/ folder structured like this — adapt it to your own repo:

  • docs/architecture/overview.md — Architecture Overview
  • docs/development/getting-started.md — Development Guide
  • docs/deployment/azure-setup.md — Deployment Guide
  • docs/api/interfaces.md — API Documentation
  • docs/troubleshooting/common-issues.md — Troubleshooting

This project is licensed under the MIT License - see the LICENSE file (in your own project’s root) for details.

  • Angular team for the amazing framework
  • Module Federation community
  • Azure Static Web Apps team

Made with ❤️ by Your Name

---
## 🎯 Blog Post Template
### **"Building Production-Ready Micro Frontends with Angular"**
```markdown
# Building Production-Ready Micro Frontends with Angular: A Complete Guide
## Introduction
Micro Frontends represent a paradigm shift in frontend architecture, extending the microservices concept to the frontend. In this comprehensive guide, I'll walk you through building a production-ready banking platform using Angular and Module Federation.
## The Challenge
Traditional monolithic frontends become unwieldy as teams and applications grow. We needed:
- **Team Independence**: Multiple teams working without conflicts
- **Technology Flexibility**: Different Angular versions per team
- **Independent Deployment**: Deploy features without full application rebuilds
- **Scalability**: Handle increasing complexity and team size
## Our Solution: Angular Micro Frontends
We chose Angular with Module Federation for several reasons:
### Why Angular?
- Mature ecosystem and tooling
- Strong TypeScript integration
- Excellent performance optimization
- Enterprise-ready features
### Why Module Federation?
- Runtime code sharing
- Independent deployment
- Shared dependency optimization
- Proven at scale
## Architecture Overview
Our banking platform consists of:
1. **Host Application**: Navigation, authentication, layout
2. **Loan Calculator MFE**: Mortgage calculations and tools
3. **User Dashboard MFE**: Account overview and management
4. **Transaction History MFE**: Financial transaction tracking
[Include architecture diagram]
## Implementation Highlights
### 1. Module Federation Configuration
The key to our setup is the webpack configuration:
```javascript
// Host application webpack.config.js
const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
"loanCalculator": "https://loan-calculator.yourdomain.com/remoteEntry.js",
"userDashboard": "https://dashboard.yourdomain.com/remoteEntry.js"
},
shared: {
"@angular/core": { singleton: true },
"@angular/common": { singleton: true }
}
})
]
};

We implemented dynamic MFE loading with error boundaries:

async loadMfe(mfeName: string) {
try {
const module = await loadRemoteModule({
remoteEntry: this.getMfeUrl(mfeName),
exposedModule: './Module'
});
return module;
} catch (error) {
this.showFallback(mfeName);
this.logError(error);
}
}

Cross-MFE communication uses an event bus pattern:

// Emit events between MFEs
this.eventBus.emit('user-updated', { userId: 123 });
// Listen for events
this.eventBus.on('user-updated').subscribe(event => {
this.updateUserDisplay(event.payload);
});

We use Azure Static Web Apps with GitHub Actions:

  1. Individual MFE Deployment: Each MFE deploys independently
  2. Host Configuration Update: Dynamic manifest for MFE URLs
  3. Rollback Capability: Independent rollback per MFE
  4. Blue-Green Deployment: Zero-downtime deployments

After optimization, we achieved:

  • First Load: 1.8 seconds (down from 4.2s)
  • MFE Load Time: 0.6 seconds average
  • Bundle Size: 420KB total (down from 1.2MB)
  • Lighthouse Score: 96/100
  • Module Federation reliability
  • Team independence and velocity
  • Deployment flexibility
  • Performance improvements
  • Initial learning curve
  • Debugging complexity
  • Shared dependency management
  • Testing strategy evolution
  1. Start Simple: Begin with clear MFE boundaries
  2. Shared Dependencies: Carefully manage shared libraries
  3. Error Handling: Implement robust fallback mechanisms
  4. Testing Strategy: Multi-level testing approach
  5. Monitoring: Comprehensive observability setup
  • Server-Side Rendering (SSR) support
  • Advanced caching strategies
  • Enhanced security patterns
  • GraphQL federation integration

Micro Frontends with Angular and Module Federation enabled our team to:

  • Scale Development: 3 teams working independently
  • Improve Performance: 50% faster load times
  • Increase Deployment Frequency: Daily deployments vs. monthly
  • Enhance User Experience: Better perceived performance

The investment in Micro Frontend architecture pays dividends in team productivity and application maintainability.


Have questions about Micro Frontends? Contact me or follow me on Twitter

---
## ✅ Documentation & Advanced Concepts Checklist
### **Documentation Complete**
- [ ] **Architecture documentation** with diagrams and patterns
- [ ] **API documentation** for all MFE interfaces
- [ ] **Development guides** for setup and contribution
- [ ] **Deployment documentation** for Azure and CI/CD
- [ ] **Troubleshooting guides** for common issues
- [ ] **Performance benchmarks** and optimization tips
- [ ] **Security guidelines** and best practices
- [ ] **Blog post** for sharing experience
- [ ] **Video tutorials** (optional)
- [ ] **Community resources** and support channels
### **Advanced Patterns Implemented**
- [ ] **Event-driven communication** between MFEs
- [ ] **Shared state management** across boundaries
- [ ] **Dynamic MFE registration** and loading
- [ ] **Error boundaries** with fallback mechanisms
- [ ] **Smart preloading** strategies
- [ ] **Security validation** for MFE sources
- [ ] **Performance monitoring** and analytics
- [ ] **Bundle optimization** and splitting
---
## 🎉 Congratulations!
You've completed the comprehensive Angular Micro Frontend tutorial series! You now have:
- ✅ **Complete MFE Platform**: Production-ready banking application
- ✅ **Advanced Patterns**: Error handling, communication, security
- ✅ **Production Deployment**: Azure Static Web Apps with CI/CD
- ✅ **Comprehensive Documentation**: Guides, APIs, and tutorials
- ✅ **Performance Optimization**: Fast loading and efficient bundling
- ✅ **Best Practices**: Security, testing, and maintainability
## 🚀 What's Next?
1. **Share Your Experience**: Write blog posts and create tutorials
2. **Contribute to Community**: Share patterns and solutions
3. **Explore Advanced Topics**: SSR, GraphQL federation, advanced caching
4. **Scale Your Implementation**: Add more MFEs and teams
5. **Monitor and Optimize**: Continuous improvement and performance tuning
Your Angular Micro Frontend journey is just beginning! 🎯