πŸ”₯ Angular Service for NgRx State Sync Across Micro Frontends

πŸš€ Simplify State Persistence & Real-Time Sync in MFEs

Β·

2 min read

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

  1. Run MFE1 and MFE2:

     ng serve mfe1 --port 4201
     ng serve mfe2 --port 4202
    
  2. Modify state in MFE1 (e.g., increment counter).

  3. Observe MFE2 updates state instantly without reload.


πŸ”₯ Final Benefits

βœ… Easier state management with a reusable service
βœ… Automatic IndexedDB persistence
βœ… Real-time sync across MFEs

Β