Angular state management has come a long way.
From services with BehaviorSubjects, to NgRx Store, to now — NgRx Signals.
If you’re building a Micro-Frontend architecture in 2026, NgRx Signals is the state management approach you should be using. Here’s why — and exactly how to implement it.
What Are NgRx Signals?
NgRx Signals is a reactive state management library built on top of Angular’s native Signals API. It gives you:
- A simple, lightweight store
- No boilerplate compared to classic NgRx
- Fine-grained reactivity
- Works perfectly across micro-apps
The Problem With State in Micro-Frontends
In a Micro-Frontend setup, each app is independent:
Shell App (Port 4200)
├── Auth App (Port 4201)
└── Dashboard App (Port 4202)
The challenge — how do you share state between them without tight coupling?
Classic NgRx is overkill for this. BehaviorSubjects get messy. NgRx Signals hits the sweet spot.
Setting Up NgRx Signals
Install the package:
npm install @ngrx/signals
Step 1 — Create a Shared Signal Store
In your Shell app, create a global store:
// shell/src/app/store/app.store.ts
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
export interface AppState {
theme: 'light' | 'dark';
language: 'en' | 'ar' | 'fr';
isAuthenticated: boolean;
user: { name: string; role: string } | null;
}
const initialState: AppState = {
theme: 'light',
language: 'en',
isAuthenticated: false,
user: null,
};
export const AppStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withMethods((store) => ({
setTheme(theme: 'light' | 'dark') {
patchState(store, { theme });
},
setLanguage(language: 'en' | 'ar' | 'fr') {
patchState(store, { language });
},
setUser(user: { name: string; role: string }) {
patchState(store, { user, isAuthenticated: true });
},
logout() {
patchState(store, { user: null, isAuthenticated: false });
},
}))
);
Step 2 — Use the Store in Your Shell Component
// shell/src/app/app.component.ts
import { Component, inject, effect } from '@angular/core';
import { AppStore } from './store/app.store';
@Component({
selector: 'app-root',
template: `
<div [class]="store.theme() === 'dark' ? 'dark-theme' : 'light-theme'"
[dir]="store.language() === 'ar' ? 'rtl' : 'ltr'">
<router-outlet />
</div>
`
})
export class AppComponent {
store = inject(AppStore);
constructor() {
// Persist theme to localStorage
effect(() => {
localStorage.setItem('theme', this.store.theme());
});
}
}
Step 3 — Access Store in Micro-Apps
Since the store is providedIn: 'root' and shared via the Shell, micro-apps can inject it directly:
// dashboard/src/app/dashboard.component.ts
import { Component, inject } from '@angular/core';
import { AppStore } from '../../../shell/src/app/store/app.store';
@Component({
selector: 'app-dashboard',
template: `
<div>
<h1>Welcome, {{ store.user()?.name }}</h1>
<button (click)="toggleTheme()">
Switch to {{ store.theme() === 'light' ? 'Dark' : 'Light' }} mode
</button>
</div>
`
})
export class DashboardComponent {
store = inject(AppStore);
toggleTheme() {
const newTheme = this.store.theme() === 'light' ? 'dark' : 'light';
this.store.setTheme(newTheme);
}
}
Step 4 — Add State Persistence
Keep state alive across page refreshes:
// shell/src/app/store/app.store.ts
import { signalStore, withState, withMethods, withHooks, patchState } from '@ngrx/signals';
export const AppStore = signalStore(
{ providedIn: 'root' },
withState<AppState>(() => ({
theme: (localStorage.getItem('theme') as 'light' | 'dark') || 'light',
language: (localStorage.getItem('language') as 'en' | 'ar' | 'fr') || 'en',
isAuthenticated: !!localStorage.getItem('auth_token'),
user: null,
})),
withMethods((store) => ({
setTheme(theme: 'light' | 'dark') {
localStorage.setItem('theme', theme);
patchState(store, { theme });
},
setLanguage(language: 'en' | 'ar' | 'fr') {
localStorage.setItem('language', language);
patchState(store, { language });
},
setUser(user: { name: string; role: string }) {
patchState(store, { user, isAuthenticated: true });
},
logout() {
localStorage.removeItem('auth_token');
patchState(store, { user: null, isAuthenticated: false });
},
}))
);
Step 5 — Feature Store for Each Micro-App
Each micro-app can also have its own local store:
// dashboard/src/app/store/dashboard.store.ts
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
interface DashboardState {
widgets: Widget[];
isLoading: boolean;
selectedWidget: Widget | null;
}
export const DashboardStore = signalStore(
withState<DashboardState>({
widgets: [],
isLoading: false,
selectedWidget: null,
}),
withMethods((store) => ({
setLoading(isLoading: boolean) {
patchState(store, { isLoading });
},
setWidgets(widgets: Widget[]) {
patchState(store, { widgets, isLoading: false });
},
selectWidget(widget: Widget) {
patchState(store, { selectedWidget: widget });
},
}))
);
NgRx Signals vs Classic NgRx
| Feature | Classic NgRx | NgRx Signals |
|---|---|---|
| Boilerplate | Actions, Reducers, Effects, Selectors | Just a store |
| Learning curve | High | Low |
| Bundle size | Larger | Smaller |
| Reactivity | Observable-based | Signal-based |
| Angular 17+ fit | Good | Excellent |
| Micro-Frontend use | Complex | Simple |
Why NgRx Signals is Perfect for Micro-Frontends
- ✅ No boilerplate — less code to share between apps
- ✅ Fine-grained updates — only components using changed signals re-render
- ✅ Simple injection — just
inject(AppStore)anywhere - ✅ Persistent state — easy localStorage integration
- ✅ TypeScript friendly — full type safety out of the box
Want This Already Set Up?
I built NgMFE Starter Kit — a complete Angular 21 Micro-Frontend boilerplate with NgRx Signals pre-configured:
- ⚡ Angular 21 + Native Federation
- 🔐 JWT Auth + Route Guards
- 🌍 Arabic RTL support
- 🌙 Dark/Light mode with NgRx Signals
- 🚀 CI/CD with GitHub Actions + Vercel
- ✅ 13 unit tests passing
🔴 Live Demo: https://ng-mfe-shell.vercel.app
(login: admin/admin)
🛒 Get the kit:
👉 https://mhmoudashour.gumroad.com/l/hdditr
💼 Need it set up for your project?
👉 https://www.upwork.com/services/product/development-it-set-up-angular-micro-frontend-architecture-for-your-enterprise-app-2037100004401414520?ref=project_share
Have questions about NgRx Signals or state management in Micro-Frontends? Drop them in the comments! 🙏
Top comments (0)