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 Deployment Guide 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 Guide for detailed guidelines.

This project is licensed under the MIT License - see the LICENSE file 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! 🎯