Angular at Scale: Introduction to Micro Frontend Architecture
Nikhil Sai
Posted on April 8, 2024
Introduction
In the evolving landscape of web development, the architecture of applications plays a crucial role in ensuring scalability, maintainability, and efficiency. One such architectural pattern that has gained significant traction is the concept of micro frontends. This approach extends the principles of microservices to the frontend, allowing teams to break down a monolithic frontend into smaller, more manageable pieces.
What are Micro Frontends?
Micro frontends are essentially segments of a frontend application that encapsulate specific functionalities or features. They can be developed, tested, and deployed independently, offering a modular approach to building complex web applications. This architecture enables multiple teams to work in parallel, each focusing on distinct aspects of the application without stepping on each other’s toes.
Benefits of Micro Frontends:
• Decoupled Codebases: Each micro frontend has its own codebase, reducing dependency conflicts and simplifying updates.
• Autonomous Teams: Teams can work independently, choosing their own technology stacks and release cycles.
• Scalable Development: New features can be developed and deployed without the need to understand the entire frontend codebase.
• Reusable Components: Common features can be shared across different parts of the application, promoting reusability.
Implementing Micro Frontends with Angular
Angular, with its component-based architecture, is well-suited for implementing micro frontends. Here’s how you can leverage Angular to build a micro frontend architecture:
Example: E-Commerce Application
Consider an e-commerce platform composed of several micro frontends such as Product Listing, Shopping Cart, and Checkout.
Product Listing Micro Frontend
// product-listing.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-product-listing',
template: `
<div *ngFor="let product of products">
<app-product-item [product]="product"></app-product-item>
</div>
`,
standalone: true
})
export class ProductListingComponent {
products = [...]; // Array of product data
}
Shopping Cart Micro Frontend
// shopping-cart.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-shopping-cart',
template: `
<div *ngFor="let item of cartItems">
<app-cart-item [item]="item"></app-cart-item>
</div>
`,
standalone: true
})
export class ShoppingCartComponent {
cartItems = [...]; // Array of items in the cart
}
Integration and Communication
Integrating
two micro frontends deployed on separate servers into a single webpage can be achieved using several methods. One popular approach is using Single-SPA
, a JavaScript framework for front-end microservices that allows you to integrate multiple micro frontend applications into one cohesive experience1.
Here’s a simplified process on how we can achieve this:
Create a Container Application: This will act as the main shell that loads and coordinates the micro frontends.
Use Module Federation: If you’re using Webpack 5, Module Federation allows you to dynamically load separate micro frontends as if they were part of the same application.
Configure Routes: Define routes in your container application that correspond to each micro frontend. When a route is accessed, the corresponding micro frontend is loaded.
Micro frontends can communicate
with each other through a shared state management system or via custom events. For instance, when a product is added to the cart in the Product Listing, it can emit an event that the Shopping Cart micro frontend listens to and updates its state accordingly.
Shared Services
Shared services are a common way to enable communication between micro frontends. These services can be injected into each micro frontend to share data and functionality.
Example: Shared Cart Service
Let's consider the e-commerce application that we created earlier. To communicate between those two front ends a CartService
can be used to manage the cart items.
// cart.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CartService {
private cartItems = new BehaviorSubject([]);
getCartItems() {
return this.cartItems.asObservable();
}
addToCart(product) {
const updatedCart = [...this.cartItems.value, product];
this.cartItems.next(updatedCart);
}
}
The CartService
uses RxJS’s BehaviorSubject
to maintain a reactive list of cart items. Any micro frontend can inject this service to add items to the cart or subscribe to the cart items stream.
Custom Events
Angular applications can also use custom events for communication. This is particularly useful when micro frontends are encapsulated within web components.
Example: Custom Event for Product Selection
The product listings micro frontend can emit a custom event when a product is selected, which the shopping cart micro frontend can listen to and respond accordingly.
// product-listings.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-product-listings',
template: `
<div *ngFor="let product of products">
<button (click)="selectProduct(product)">Add to Cart</button>
</div>
`,
standalone: true
})
export class ProductListingsComponent {
@Output() productSelected = new EventEmitter();
products = [...]; // Array of product data
selectProduct(product) {
this.productSelected.emit(product);
}
}
When a product is selected, the productSelected
event is emitted. The shopping cart micro frontend can then listen for this event and update the cart accordingly.
State Management Libraries
For more complex scenarios, state management libraries like NgRx or Akita can be used to manage the state across micro frontends.
Example: NgRx Store for User Authentication
If user authentication status needs to be shared across micro frontends, an NgRx store can be set up to manage the authentication state.
// auth.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { login, logout } from './auth.actions';
export const initialState = { loggedIn: false };
const _authReducer = createReducer(
initialState,
on(login, state => ({ ...state, loggedIn: true })),
on(logout, state => ({ ...state, loggedIn: false }))
);
export function authReducer(state, action) {
return _authReducer(state, action);
}
The authReducer
manages the login state, and any micro frontend can dispatch actions to log in or log out the user, with the state being reactive and accessible across the application.
Use Cases
•Large Enterprises: Micro frontends are ideal for large enterprises where different teams work on different parts of the application.
•Scalable Startups: Startups that anticipate rapid growth can benefit from the scalability offered by micro frontends.
Conclusion
Micro frontend architecture in Angular offers a path towards a more modular and efficient development process and a flexible way to build scalable applications. By embracing this pattern, organizations can foster better collaboration among teams, streamline the development process, and ultimately deliver a more robust and user-friendly application.
Thank's for your time, see you soon in another one :)
Posted on April 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.