Skip to content

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.

  1. Navigation & Routing: Global navigation and route management
  2. Authentication: Centralized user authentication and authorization
  3. Layout Management: Common headers, footers, and layout structure
  4. MFE Loading: Dynamic loading and management of remote MFEs
  5. State Management: Shared application state across MFEs
  6. Error Handling: Global error handling and fallback mechanisms
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”
Terminal window
cd host-banking-app
ng generate module layout
ng generate component layout/header
ng generate component layout/footer
ng generate component layout/sidebar
ng generate component layout/main-layout

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;
}
}

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>&copy; 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>

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();
}
}

Terminal window
ng generate module auth --routing
ng generate component auth/login
ng generate component auth/register
ng generate service auth/auth
ng generate guard auth/auth
ng generate interface models/user

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;
}

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();
}
}
}
}

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;
}
}

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;
}
}

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('');
}
}

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';
}
}

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

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;

  • 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
  • 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

Your host application is now fully configured! Next steps:

  1. Host Application Setup Complete
  2. ➡️ Continue to: Remote Configuration - Configure and optimize remote MFEs
  3. 📚 Alternative: Local Testing - Test the complete integration

Your host application now provides a solid foundation for your Micro Frontend architecture! 🎉