Angular MFE Host Application Setup | Complete Configuration Guide
The host application (shell) is the central hub of your Micro Frontend architecture. It manages routing, navigation, authentication, and loads remote MFEs at runtime. This guide provides comprehensive instructions for building a robust host application.
🏗️ Host Application Architecture
Section titled “🏗️ Host Application Architecture”Core Responsibilities
Section titled “Core Responsibilities”- Navigation & Routing: Global navigation and route management
- Authentication: Centralized user authentication and authorization
- Layout Management: Common headers, footers, and layout structure
- MFE Loading: Dynamic loading and management of remote MFEs
- State Management: Shared application state across MFEs
- Error Handling: Global error handling and fallback mechanisms
Application Structure
Section titled “Application Structure”host-banking-app/├── src/│ ├── app/│ │ ├── core/ # Core services and guards│ │ ├── shared/ # Shared components and utilities│ │ ├── layout/ # Layout components│ │ ├── auth/ # Authentication module│ │ ├── services/ # Application services│ │ └── guards/ # Route guards│ ├── assets/│ │ ├── mf.manifest.json # MFE configuration│ │ └── styles/ # Global styles│ └── environments/ # Environment configurations
🎨 Step 1: Enhanced Layout and Navigation
Section titled “🎨 Step 1: Enhanced Layout and Navigation”1.1 Create Layout Module
Section titled “1.1 Create Layout Module”cd host-banking-appng generate module layoutng generate component layout/headerng generate component layout/footerng generate component layout/sidebarng generate component layout/main-layout
1.2 Create Header Component
Section titled “1.2 Create Header Component”header.component.html:
<header class="app-header"> <div class="header-container"> <!-- Brand Section --> <div class="brand-section"> <div class="logo"> <img src="/assets/images/logo.png" alt="Banking Logo" class="logo-img"> <h1 class="brand-title">BankingPlatform</h1> </div> </div>
<!-- Navigation Section --> <nav class="main-navigation" [class.mobile-open]="isMobileMenuOpen"> <ul class="nav-menu"> <li class="nav-item"> <a routerLink="/dashboard" routerLinkActive="active" class="nav-link" (click)="closeMobileMenu()"> <i class="icon dashboard-icon">📊</i> <span>Dashboard</span> </a> </li> <li class="nav-item"> <a routerLink="/loan-calculator" routerLinkActive="active" class="nav-link" (click)="closeMobileMenu()"> <i class="icon calculator-icon">🏠</i> <span>Loan Calculator</span> </a> </li> <li class="nav-item"> <a routerLink="/transactions" routerLinkActive="active" class="nav-link" (click)="closeMobileMenu()"> <i class="icon transaction-icon">💳</i> <span>Transactions</span> </a> </li> <li class="nav-item dropdown" [class.open]="isServicesDropdownOpen"> <a class="nav-link dropdown-toggle" (click)="toggleServicesDropdown()"> <i class="icon services-icon">🛠️</i> <span>Services</span> <i class="dropdown-arrow">▼</i> </a> <ul class="dropdown-menu"> <li><a routerLink="/services/investments" (click)="closeMobileMenu()">Investments</a></li> <li><a routerLink="/services/insurance" (click)="closeMobileMenu()">Insurance</a></li> <li><a routerLink="/services/support" (click)="closeMobileMenu()">Support</a></li> </ul> </li> </ul> </nav>
<!-- User Section --> <div class="user-section"> <div class="notifications" *ngIf="user"> <button class="notification-btn" [class.has-notifications]="hasNotifications"> <i class="icon">🔔</i> <span class="notification-count" *ngIf="notificationCount > 0"> {{ notificationCount }} </span> </button> </div>
<div class="user-profile" *ngIf="user; else loginSection"> <div class="user-info" (click)="toggleUserDropdown()"> <img [src]="user.avatar || '/assets/images/default-avatar.png'" [alt]="user.name" class="user-avatar"> <div class="user-details"> <span class="user-name">{{ user.name }}</span> <span class="user-role">{{ user.role }}</span> </div> <i class="dropdown-arrow">▼</i> </div>
<div class="user-dropdown" [class.open]="isUserDropdownOpen"> <a href="#" class="dropdown-item" (click)="viewProfile()"> <i class="icon">👤</i> Profile </a> <a href="#" class="dropdown-item" (click)="viewSettings()"> <i class="icon">⚙️</i> Settings </a> <div class="dropdown-divider"></div> <a href="#" class="dropdown-item logout" (click)="logout()"> <i class="icon">🚪</i> Logout </a> </div> </div>
<ng-template #loginSection> <div class="auth-buttons"> <button class="btn btn-outline" (click)="login()">Login</button> <button class="btn btn-primary" (click)="register()">Sign Up</button> </div> </ng-template> </div>
<!-- Mobile Menu Toggle --> <button class="mobile-menu-toggle" (click)="toggleMobileMenu()" [class.active]="isMobileMenuOpen"> <span class="hamburger-line"></span> <span class="hamburger-line"></span> <span class="hamburger-line"></span> </button> </div></header>
header.component.ts:
import { Component, OnInit, HostListener } from '@angular/core';import { Router } from '@angular/router';import { AuthService } from '../../auth/auth.service';import { User } from '../../models/user.interface';
@Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss']})export class HeaderComponent implements OnInit { user: User | null = null; isMobileMenuOpen = false; isUserDropdownOpen = false; isServicesDropdownOpen = false; hasNotifications = true; notificationCount = 3;
constructor( private authService: AuthService, private router: Router ) { }
ngOnInit(): void { this.authService.currentUser$.subscribe(user => { this.user = user; }); }
@HostListener('document:click', ['$event']) onDocumentClick(event: Event): void { const target = event.target as HTMLElement; if (!target.closest('.user-profile')) { this.isUserDropdownOpen = false; } if (!target.closest('.dropdown')) { this.isServicesDropdownOpen = false; } }
toggleMobileMenu(): void { this.isMobileMenuOpen = !this.isMobileMenuOpen; }
closeMobileMenu(): void { this.isMobileMenuOpen = false; }
toggleUserDropdown(): void { this.isUserDropdownOpen = !this.isUserDropdownOpen; }
toggleServicesDropdown(): void { this.isServicesDropdownOpen = !this.isServicesDropdownOpen; }
login(): void { this.router.navigate(['/auth/login']); }
register(): void { this.router.navigate(['/auth/register']); }
logout(): void { this.authService.logout(); this.router.navigate(['/']); }
viewProfile(): void { this.router.navigate(['/profile']); this.isUserDropdownOpen = false; }
viewSettings(): void { this.router.navigate(['/settings']); this.isUserDropdownOpen = false; }}
1.3 Create Footer Component
Section titled “1.3 Create Footer Component”footer.component.html:
<footer class="app-footer"> <div class="footer-container"> <div class="footer-section"> <div class="footer-brand"> <h3>BankingPlatform</h3> <p>Secure, reliable, and innovative banking solutions for the modern world.</p> </div> </div>
<div class="footer-section"> <h4>Quick Links</h4> <ul class="footer-links"> <li><a routerLink="/dashboard">Dashboard</a></li> <li><a routerLink="/loan-calculator">Loan Calculator</a></li> <li><a routerLink="/transactions">Transactions</a></li> <li><a routerLink="/support">Support</a></li> </ul> </div>
<div class="footer-section"> <h4>Services</h4> <ul class="footer-links"> <li><a href="#">Personal Banking</a></li> <li><a href="#">Business Banking</a></li> <li><a href="#">Investments</a></li> <li><a href="#">Insurance</a></li> </ul> </div>
<div class="footer-section"> <h4>Contact</h4> <div class="contact-info"> <p><i class="icon">📞</i> 1-800-BANKING</p> <p><i class="icon">✉️</i> support@bankingplatform.com</p> <p><i class="icon">📍</i> 123 Banking St, Finance City</p> </div> </div>
<div class="footer-section"> <h4>Follow Us</h4> <div class="social-links"> <a href="#" class="social-link"><i class="icon">📘</i></a> <a href="#" class="social-link"><i class="icon">🐦</i></a> <a href="#" class="social-link"><i class="icon">📷</i></a> <a href="#" class="social-link"><i class="icon">💼</i></a> </div> </div> </div>
<div class="footer-bottom"> <div class="footer-container"> <div class="footer-bottom-content"> <p>© 2024 BankingPlatform. All rights reserved.</p> <div class="footer-bottom-links"> <a href="#">Privacy Policy</a> <a href="#">Terms of Service</a> <a href="#">Security</a> </div> </div> </div> </div></footer>
1.4 Create Main Layout Component
Section titled “1.4 Create Main Layout Component”main-layout.component.html:
<div class="app-layout"> <app-header></app-header>
<main class="main-content" [class.has-sidebar]="showSidebar"> <app-sidebar *ngIf="showSidebar"></app-sidebar>
<div class="content-area"> <!-- Loading Indicator --> <div class="loading-overlay" *ngIf="isLoading"> <div class="loading-spinner"> <div class="spinner"></div> <p>Loading...</p> </div> </div>
<!-- Error Display --> <div class="error-banner" *ngIf="errorMessage"> <div class="error-content"> <i class="error-icon">⚠️</i> <span>{{ errorMessage }}</span> <button class="error-close" (click)="clearError()">✕</button> </div> </div>
<!-- MFE Content --> <div class="mfe-container"> <router-outlet></router-outlet> </div> </div> </main>
<app-footer></app-footer></div>
main-layout.component.ts:
import { Component, OnInit, OnDestroy } from '@angular/core';import { NavigationEnd, Router } from '@angular/router';import { Subject, takeUntil } from 'rxjs';import { LoadingService } from '../services/loading.service';import { ErrorService } from '../services/error.service';
@Component({ selector: 'app-main-layout', templateUrl: './main-layout.component.html', styleUrls: ['./main-layout.component.scss']})export class MainLayoutComponent implements OnInit, OnDestroy { showSidebar = false; isLoading = false; errorMessage = ''; private destroy$ = new Subject<void>();
constructor( private router: Router, private loadingService: LoadingService, private errorService: ErrorService ) { }
ngOnInit(): void { // Subscribe to loading state this.loadingService.loading$ .pipe(takeUntil(this.destroy$)) .subscribe(loading => this.isLoading = loading);
// Subscribe to error messages this.errorService.error$ .pipe(takeUntil(this.destroy$)) .subscribe(error => this.errorMessage = error);
// Handle route changes this.router.events .pipe(takeUntil(this.destroy$)) .subscribe(event => { if (event instanceof NavigationEnd) { // Determine if sidebar should be shown based on route this.showSidebar = this.shouldShowSidebar(event.url); // Clear errors on route change this.clearError(); } }); }
ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
private shouldShowSidebar(url: string): boolean { // Show sidebar only on specific routes const sidebarRoutes = ['/dashboard']; return sidebarRoutes.some(route => url.startsWith(route)); }
clearError(): void { this.errorService.clearError(); }}
🔐 Step 2: Authentication Module
Section titled “🔐 Step 2: Authentication Module”2.1 Create Authentication Module
Section titled “2.1 Create Authentication Module”ng generate module auth --routingng generate component auth/loginng generate component auth/registerng generate service auth/authng generate guard auth/authng generate interface models/user
2.2 User Interface
Section titled “2.2 User Interface”models/user.interface.ts:
export interface User { id: string; email: string; name: string; role: 'customer' | 'admin' | 'manager'; avatar?: string; permissions: string[]; lastLogin?: Date; preferences: { theme: 'light' | 'dark'; language: string; notifications: boolean; };}
export interface LoginCredentials { email: string; password: string; rememberMe?: boolean;}
export interface RegisterData { email: string; password: string; confirmPassword: string; firstName: string; lastName: string; phone?: string;}
2.3 Authentication Service
Section titled “2.3 Authentication Service”auth/auth.service.ts:
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';import { BehaviorSubject, Observable, of, throwError } from 'rxjs';import { map, catchError, tap } from 'rxjs/operators';import { User, LoginCredentials, RegisterData } from '../models/user.interface';
@Injectable({ providedIn: 'root'})export class AuthService { private currentUserSubject = new BehaviorSubject<User | null>(null); public currentUser$ = this.currentUserSubject.asObservable();
private tokenKey = 'banking_auth_token'; private userKey = 'banking_user_data';
constructor(private http: HttpClient) { // Load user from localStorage on service initialization this.loadUserFromStorage(); }
get currentUser(): User | null { return this.currentUserSubject.value; }
get isAuthenticated(): boolean { return !!this.currentUser && !!this.getToken(); }
login(credentials: LoginCredentials): Observable<User> { // In a real app, this would call your authentication API // For demo purposes, we'll use mock authentication return this.mockLogin(credentials).pipe( tap(user => { this.setAuthData(user, 'mock-jwt-token'); }), catchError(error => { console.error('Login error:', error); return throwError(error); }) ); }
register(registerData: RegisterData): Observable<User> { // In a real app, this would call your registration API return this.mockRegister(registerData).pipe( tap(user => { this.setAuthData(user, 'mock-jwt-token'); }), catchError(error => { console.error('Registration error:', error); return throwError(error); }) ); }
logout(): void { localStorage.removeItem(this.tokenKey); localStorage.removeItem(this.userKey); this.currentUserSubject.next(null); }
refreshToken(): Observable<string> { // In a real app, refresh the JWT token return of('refreshed-mock-jwt-token').pipe( tap(token => { localStorage.setItem(this.tokenKey, token); }) ); }
getToken(): string | null { return localStorage.getItem(this.tokenKey); }
private mockLogin(credentials: LoginCredentials): Observable<User> { // Mock authentication logic const mockUser: User = { id: '1', email: credentials.email, name: 'John Doe', role: 'customer', avatar: '/assets/images/default-avatar.png', permissions: ['dashboard:read', 'transactions:read', 'loans:calculate'], lastLogin: new Date(), preferences: { theme: 'light', language: 'en', notifications: true } };
// Simulate API delay return new Promise((resolve) => { setTimeout(() => { if (credentials.email === 'demo@banking.com' && credentials.password === 'demo123') { resolve(mockUser); } else { throw new Error('Invalid credentials'); } }, 1000); }).then(user => of(user as User)).catch(error => throwError(error)); }
private mockRegister(registerData: RegisterData): Observable<User> { const mockUser: User = { id: Date.now().toString(), email: registerData.email, name: `${registerData.firstName} ${registerData.lastName}`, role: 'customer', permissions: ['dashboard:read', 'transactions:read', 'loans:calculate'], lastLogin: new Date(), preferences: { theme: 'light', language: 'en', notifications: true } };
return new Promise((resolve) => { setTimeout(() => resolve(mockUser), 1000); }).then(user => of(user as User)); }
private setAuthData(user: User, token: string): void { localStorage.setItem(this.tokenKey, token); localStorage.setItem(this.userKey, JSON.stringify(user)); this.currentUserSubject.next(user); }
private loadUserFromStorage(): void { const userData = localStorage.getItem(this.userKey); const token = localStorage.getItem(this.tokenKey);
if (userData && token) { try { const user = JSON.parse(userData); this.currentUserSubject.next(user); } catch (error) { console.error('Error loading user from storage:', error); this.logout(); } } }}
2.4 Authentication Guard
Section titled “2.4 Authentication Guard”auth/auth.guard.ts:
import { Injectable } from '@angular/core';import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';import { Observable } from 'rxjs';import { AuthService } from './auth.service';
@Injectable({ providedIn: 'root'})export class AuthGuard implements CanActivate {
constructor( private authService: AuthService, private router: Router ) {}
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> | Promise<boolean> | boolean {
if (this.authService.isAuthenticated) { return true; }
// Store the attempted URL for redirecting after login localStorage.setItem('returnUrl', state.url);
// Redirect to login page this.router.navigate(['/auth/login']); return false; }}
⚙️ Step 3: Core Services
Section titled “⚙️ Step 3: Core Services”3.1 Loading Service
Section titled “3.1 Loading Service”services/loading.service.ts:
import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root'})export class LoadingService { private loadingSubject = new BehaviorSubject<boolean>(false); public loading$ = this.loadingSubject.asObservable();
private activeRequests = 0;
show(): void { this.activeRequests++; this.loadingSubject.next(true); }
hide(): void { this.activeRequests--; if (this.activeRequests <= 0) { this.activeRequests = 0; this.loadingSubject.next(false); } }
isLoading(): boolean { return this.loadingSubject.value; }}
3.2 Error Service
Section titled “3.2 Error Service”services/error.service.ts:
import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root'})export class ErrorService { private errorSubject = new BehaviorSubject<string>(''); public error$ = this.errorSubject.asObservable();
showError(message: string): void { this.errorSubject.next(message);
// Auto-clear error after 5 seconds setTimeout(() => { this.clearError(); }, 5000); }
clearError(): void { this.errorSubject.next(''); }}
3.3 MFE Configuration Service (Enhanced)
Section titled “3.3 MFE Configuration Service (Enhanced)”services/mfe-config.service.ts:
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';import { BehaviorSubject, Observable, throwError } from 'rxjs';import { catchError, map, tap } from 'rxjs/operators';import { environment } from '../../environments/environment';
export interface MfeConfig { [key: string]: string;}
export interface MfeInfo { name: string; url: string; exposedModule: string; displayName: string; description: string; version: string; health: 'healthy' | 'unhealthy' | 'unknown';}
@Injectable({ providedIn: 'root'})export class MfeConfigService { private configSubject = new BehaviorSubject<MfeConfig>({}); public config$ = this.configSubject.asObservable();
private mfeHealthSubject = new BehaviorSubject<{ [key: string]: MfeInfo }>({}); public mfeHealth$ = this.mfeHealthSubject.asObservable();
constructor(private http: HttpClient) { this.loadConfig(); }
private async loadConfig(): Promise<void> { try { const config = await this.http.get<any>('/assets/mf.manifest.json').toPromise(); const envConfig = config[environment.production ? 'production' : 'development']; this.configSubject.next(envConfig);
// Check health of all MFEs this.checkMfeHealth(envConfig); } catch (error) { console.error('Failed to load MFE configuration:', error); } }
private async checkMfeHealth(config: MfeConfig): Promise<void> { const healthChecks: { [key: string]: MfeInfo } = {};
for (const [name, url] of Object.entries(config)) { try { const response = await fetch(url, { method: 'HEAD' }); healthChecks[name] = { name, url, exposedModule: `./Module`, displayName: this.getDisplayName(name), description: this.getDescription(name), version: '1.0.0', health: response.ok ? 'healthy' : 'unhealthy' }; } catch (error) { healthChecks[name] = { name, url, exposedModule: `./Module`, displayName: this.getDisplayName(name), description: this.getDescription(name), version: '1.0.0', health: 'unhealthy' }; } }
this.mfeHealthSubject.next(healthChecks); }
private getDisplayName(mfeName: string): string { const displayNames: { [key: string]: string } = { 'mfeLoanCalculator': 'Loan Calculator', 'mfeUserDashboard': 'User Dashboard', 'mfeTransactionHistory': 'Transaction History' }; return displayNames[mfeName] || mfeName; }
private getDescription(mfeName: string): string { const descriptions: { [key: string]: string } = { 'mfeLoanCalculator': 'Calculate loan payments and interest rates', 'mfeUserDashboard': 'View account balances and user information', 'mfeTransactionHistory': 'Browse transaction history and statements' }; return descriptions[mfeName] || 'MFE Module'; }
getMfeUrl(mfeName: string): string | null { const config = this.configSubject.value; return config[mfeName] || null; }
isHealthy(mfeName: string): boolean { const health = this.mfeHealthSubject.value; return health[mfeName]?.health === 'healthy'; }}
🎨 Step 4: Enhanced Styling
Section titled “🎨 Step 4: Enhanced Styling”4.1 Global Styles Structure
Section titled “4.1 Global Styles Structure”Create organized SCSS structure:
src/styles/├── abstracts/│ ├── _variables.scss│ ├── _mixins.scss│ └── _functions.scss├── base/│ ├── _reset.scss│ ├── _typography.scss│ └── _utilities.scss├── components/│ ├── _buttons.scss│ ├── _forms.scss│ └── _cards.scss├── layout/│ ├── _header.scss│ ├── _footer.scss│ └── _sidebar.scss└── styles.scss
4.2 Design System Variables
Section titled “4.2 Design System Variables”styles/abstracts/_variables.scss:
// Colors$primary-color: #2c3e50;$secondary-color: #3498db;$accent-color: #e74c3c;$success-color: #27ae60;$warning-color: #f39c12;$error-color: #e74c3c;
$text-primary: #2c3e50;$text-secondary: #7f8c8d;$text-muted: #95a5a6;
$background-primary: #ffffff;$background-secondary: #f8f9fa;$background-dark: #2c3e50;
$border-color: #dee2e6;$border-radius: 8px;$border-radius-small: 4px;
// Typography$font-family-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;$font-family-mono: 'Fira Code', 'Monaco', 'Consolas', monospace;
$font-size-xs: 0.75rem;$font-size-sm: 0.875rem;$font-size-base: 1rem;$font-size-lg: 1.125rem;$font-size-xl: 1.25rem;$font-size-2xl: 1.5rem;$font-size-3xl: 1.875rem;
// Spacing$spacer: 1rem;$spacers: ( 0: 0, 1: ($spacer * 0.25), 2: ($spacer * 0.5), 3: $spacer, 4: ($spacer * 1.5), 5: ($spacer * 3));
// Breakpoints$breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px);
// Shadows$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);$shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);$shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);$shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
// Transitions$transition-base: all 0.3s ease;$transition-fast: all 0.15s ease;$transition-slow: all 0.5s ease;
✅ Host Application Setup Verification
Section titled “✅ Host Application Setup Verification”Checklist
Section titled “Checklist”- Layout module created with header, footer, sidebar
- Navigation system implemented with mobile support
- Authentication module with login/register components
- User management and session handling
- Route guards protecting secured routes
- Loading and error services for global state management
- Enhanced MFE configuration service with health checking
- Design system with consistent styling
- Responsive design for mobile and desktop
- Accessibility features implemented
Expected Host App Features
Section titled “Expected Host App Features”- ✅ Professional navigation with dropdown menus
- ✅ User authentication with mock login system
- ✅ Responsive design for all screen sizes
- ✅ Loading states and error handling
- ✅ MFE health monitoring and fallback handling
- ✅ Consistent styling across the application
🚀 What’s Next?
Section titled “🚀 What’s Next?”Your host application is now fully configured! Next steps:
- ✅ Host Application Setup Complete
- ➡️ Continue to: Remote Configuration - Configure and optimize remote MFEs
- 📚 Alternative: Local Testing - Test the complete integration
Your host application now provides a solid foundation for your Micro Frontend architecture! 🎉