Photo by Adi Goldstein on Unsplash
π₯ Angular Service for NgRx State Sync Across Micro Frontends
π Simplify State Persistence & Real-Time Sync in MFEs
To simplify NgRx state persistence and real-time syncing in multiple Micro Frontends (MFEs), let's build a State Management Service that:
β
Persists state in IndexedDB (efficient & scalable)
β
Syncs state across MFEs using BroadcastChannel
β
Provides a simple API for easy integration
π 1. Create the State Sync Service
Create projects/shared/src/lib/state-sync.service.ts
:
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { BehaviorSubject } from 'rxjs';
import { get, set } from 'idb-keyval';
@Injectable({
providedIn: 'root',
})
export class StateSyncService {
private channel = new BroadcastChannel('ngrx-sync');
private state$ = new BehaviorSubject<any>(null);
constructor(private store: Store) {
this.initialize();
}
async initialize() {
// Load state from IndexedDB on startup
const savedState = await get('appState');
if (savedState) {
this.store.dispatch({ type: '[State] Load from IndexedDB', payload: savedState });
}
// Listen for state updates from other MFEs
this.channel.onmessage = (event) => {
console.log('π State updated from another MFE:', event.data);
this.store.dispatch({ type: '[State] Sync from BroadcastChannel', payload: event.data });
};
}
async saveState(state: any) {
await set('appState', state);
this.channel.postMessage(state); // π Broadcast state change
}
// Expose an Observable to track state changes
getStateUpdates() {
return this.state$.asObservable();
}
}
π 2. Modify the NgRx MetaReducer to Use the Service
Modify projects/mfe1/src/app/store/meta.reducer.ts
:
import { ActionReducer } from '@ngrx/store';
import { StateSyncService } from 'shared-lib';
export function stateSyncMetaReducer<S>(reducer: ActionReducer<S>, stateSyncService: StateSyncService): ActionReducer<S> {
return async (state, action) => {
const nextState = reducer(state, action);
await stateSyncService.saveState(nextState); // Save and sync state
return nextState;
};
}
π 3. Inject Service in MFE Module
Modify projects/mfe1/src/app/app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule, MetaReducer } from '@ngrx/store';
import { counterReducer } from './store/counter.reducer';
import { stateSyncMetaReducer } from './store/meta.reducer';
import { StateSyncService } from 'shared-lib';
import { AppComponent } from './app.component';
const metaReducers: MetaReducer<any>[] = [];
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
StoreModule.forRoot({ count: counterReducer }, { metaReducers }), // Apply MetaReducer
],
providers: [
StateSyncService,
{
provide: 'STATE_SYNC_META',
useFactory: (service: StateSyncService) => stateSyncMetaReducer(service),
deps: [StateSyncService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
π 4. Test Real-Time State Sync
Run MFE1 and MFE2:
ng serve mfe1 --port 4201 ng serve mfe2 --port 4202
Modify state in MFE1 (e.g., increment counter).
Observe MFE2 updates state instantly without reload.
π₯ Final Benefits
β
Easier state management with a reusable service
β
Automatic IndexedDB persistence
β
Real-time sync across MFEs
Β