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-layout1.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/user2.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.scss4.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! π