05
Module 5: Angular Host and Remote Applications with Module Federation
Chapter 5 • Intermediate
30 min
Angular Host and Remote Applications with Module Federation
A Micro Frontend system consists of:
- Host Application - Application shell, routing orchestration, layout and navigation
- Remote Applications - Fully independent Angular apps that expose modules to the Host
Users only access the Host.
Host Application
Responsibilities
- Application Shell - Main container, layout, navigation
- Routing Orchestration - Routes to appropriate Remote apps
- Shared Layout - Header, footer, sidebar
- Global State - User authentication, theme, etc.
Characteristics
- Minimal Business Logic - Mostly infrastructure
- Rarely Changes - Stable foundation
- Deploys Infrequently - Only when infrastructure changes
- Entry Point - Users always start here
Example Structure
code
host-app/
├── src/
│ ├── app/
│ │ ├── shell/ # Application shell
│ │ ├── layout/ # Layout components
│ │ ├── routing/ # Route configuration
│ │ └── shared/ # Shared services
│ └── assets/
└── webpack.config.js # Module Federation config
Remote Applications
Responsibilities
- Business Domain Logic - Complete feature implementation
- Domain-Specific UI - Components for the domain
- Domain State - State management for the domain
- Module Exposure - Expose modules to Host
Characteristics
- Fully Independent - Can run standalone
- Complete Applications - Not just components
- Frequent Changes - Active development
- Independent Deployment - Deploy on own schedule
Example Structure
code
products-app/
├── src/
│ ├── app/
│ │ ├── products/ # Products feature
│ │ ├── product-detail/ # Product detail
│ │ └── shared/ # Domain-specific shared
│ └── assets/
└── webpack.config.js # Module Federation config
Host Application Architecture
Routing Configuration
typescript.js
// host-app/src/app/app-routing.module.ts
const routes: Routes = [
{
path: 'products',
loadChildren: () =>
import('products/ProductsModule').then(m => m.ProductsModule)
},
{
path: 'checkout',
loadChildren: () =>
import('checkout/CheckoutModule').then(m => m.CheckoutModule)
},
{
path: '',
redirectTo: '/products',
pathMatch: 'full'
}
]
Shell Component
typescript.js
// host-app/src/app/shell/shell.component.ts
@Component({
selector: 'app-shell',
template: `
<app-header></app-header>
<main>
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
`
})
export class ShellComponent {}
Module Federation Config
typescript.js
// host-app/webpack.config.js
new ModuleFederationPlugin({
name: 'host',
remotes: {
products: 'products@http://localhost:4201/remoteEntry.js',
checkout: 'checkout@http://localhost:4202/remoteEntry.js'
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true },
'@angular/router': { singleton: true, strictVersion: true }
}
})
Remote Application Architecture
Module Exposure
typescript.js
// products-app/src/app/products/products.module.ts
@NgModule({
declarations: [ProductsComponent, ProductDetailComponent],
imports: [CommonModule, RouterModule],
exports: [ProductsComponent]
})
export class ProductsModule {}
Routing in Remote
typescript.js
// products-app/src/app/products/products-routing.module.ts
const routes: Routes = [
{ path: '', component: ProductsComponent },
{ path: ':id', component: ProductDetailComponent }
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductsRoutingModule {}
Module Federation Config
typescript.js
// products-app/webpack.config.js
new ModuleFederationPlugin({
name: 'products',
filename: 'remoteEntry.js',
exposes: {
'./ProductsModule': './src/app/products/products.module.ts'
},
shared: {
'@angular/core': { singleton: true, strictVersion: true },
'@angular/common': { singleton: true, strictVersion: true },
'@angular/router': { singleton: true, strictVersion: true }
}
})
Communication Patterns
1. Routing-Based Communication
- Host routes to Remote
- URL-based navigation
- Standard Angular routing
2. Event-Based Communication
typescript.js
// Shared event bus
@Injectable({ providedIn: 'root' })
export class EventBus {
private subject = new Subject<any>()
emit(event: string, data: any) {
this.subject.next({ event, data })
}
on(event: string): Observable<any> {
return this.subject.pipe(
filter(e => e.event === event),
map(e => e.data)
)
}
}
3. Shared State Service
typescript.js
// Shared state service
@Injectable({ providedIn: 'root' })
export class SharedStateService {
private userSubject = new BehaviorSubject<User | null>(null)
user$ = this.userSubject.asObservable()
setUser(user: User) {
this.userSubject.next(user)
}
}
Standalone Mode
Remote applications should work standalone:
Standalone Routing
typescript.js
// products-app/src/app/app-routing.module.ts
const routes: Routes = [
{ path: 'products', component: ProductsComponent },
{ path: 'products/:id', component: ProductDetailComponent }
]
Benefits
- ✅ Development without Host
- ✅ Testing in isolation
- ✅ Independent deployment
- ✅ Fallback if Host fails
Development Workflow
Local Development
- Start Host app:
ng serve host-app --port 4200 - Start Products Remote:
ng serve products-app --port 4201 - Start Checkout Remote:
ng serve checkout-app --port 4202 - Access Host at
http://localhost:4200
Independent Development
- Remote apps can run standalone
- Test features in isolation
- No need to start all apps
Deployment Architecture
Host Deployment
- Deploys to:
https://app.example.com - Serves: Application shell
- Rarely changes
Remote Deployments
- Products:
https://products.example.com - Checkout:
https://checkout.example.com - Deploy independently
- Host references via URLs
Best Practices
Host Application
- ✅ Keep minimal and stable
- ✅ Handle global concerns only
- ✅ Provide clear routing structure
- ✅ Manage shared dependencies
Remote Applications
- ✅ Make them fully functional standalone
- ✅ Expose clear module boundaries
- ✅ Minimize dependencies on Host
- ✅ Handle errors gracefully
The next module explains when to use this architecture.