πŸ› οΈ Implementing Shared State in Angular Micro Frontends using NgRx + Module Federation

Learn how to share state across Angular micro frontends using NgRx and Module Federation for seamless communication between MFEs.

Β·

3 min read

In a Micro Frontend (MFE) architecture, each app runs independently, but sometimes we need to share state between them (e.g., authentication, user preferences, cart data in an e-commerce app).
βœ… We will use NgRx (Redux for Angular) to manage state across MFEs.
βœ… We will use Module Federation to expose and consume shared state.


πŸ“Œ Step 1: Install NgRx in Both Applications

Run the following command in both the shell and remote apps:

ng add @ngrx/store @ngrx/effects @ngrx/store-devtools

This installs NgRx packages.


πŸ“Œ Step 2: Define a Shared Store in Remote App (MFE1)

MFE1 will store and expose a counter state.

1️⃣ Create a Feature State (Counter) in MFE1

Inside projects/mfe1/src/app/store/, create the following files:

πŸ”Ή counter.actions.ts

import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');

πŸ”Ή counter.reducer.ts

import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export const initialState = 0;

export const counterReducer = createReducer(
  initialState,
  on(increment, (state) => state + 1),
  on(decrement, (state) => state - 1),
  on(reset, () => 0)
);

πŸ”Ή app.module.ts (Integrate NgRx Store)

Modify projects/mfe1/src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './store/counter.reducer';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ count: counterReducer }), // Register counter state
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

πŸ“Œ Step 3: Expose the NgRx Store from MFE1

Modify projects/mfe1/webpack.config.js to expose the store:

const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  output: {
    publicPath: "http://localhost:4201/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "mfe1",
      filename: "remoteEntry.js",
      exposes: {
        "./Store": "./src/app/store/counter.reducer",
        "./Actions": "./src/app/store/counter.actions",
      },
      shared: ["@angular/core", "@angular/common", "@ngrx/store"],
    }),
  ],
};

πŸ” What’s Happening Here?

  • exposes: { "./Store": "./src/app/store/counter.reducer" }
    β†’ Allows other MFEs to import and use the store from MFE1.

πŸ“Œ Step 4: Import Shared Store in Shell App

Now, let's consume the state from MFE1 in the Shell (Host) App.

πŸ”Ή Import Remote Store in app.module.ts

Modify projects/shell/src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from 'mfe1/Store'; // Import remote reducer
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ count: counterReducer }), // Register remote store
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

πŸ” What's Happening?

  • import { counterReducer } from 'mfe1/Store'
    β†’ Dynamically imports the counter state from MFE1.

  • StoreModule.forRoot({ count: counterReducer })
    β†’ Integrates MFE1's store into the Shell App.


πŸ“Œ Step 5: Dispatch Actions from the Shell App

Modify projects/shell/src/app/app.component.ts:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { increment, decrement, reset } from 'mfe1/Actions';

@Component({
  selector: 'app-root',
  template: `
    <h1>Micro Frontend Shared State with NgRx</h1>
    <h2>Count: {{ count$ | async }}</h2>
    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>
    <button (click)="reset()">Reset</button>
  `,
})
export class AppComponent {
  count$ = this.store.select('count');

  constructor(private store: Store<{ count: number }>) {}

  increment() {
    this.store.dispatch(increment());
  }

  decrement() {
    this.store.dispatch(decrement());
  }

  reset() {
    this.store.dispatch(reset());
  }
}

πŸ” What’s Happening?

  • The Shell App selects count from the shared store.

  • Buttons dispatch actions (increment(), decrement(), reset()) to update state in MFE1.


πŸ“Œ Step 6: Running the Shared State Micro Frontend

  1. Start the Remote MFE1:
ng serve mfe1 --port 4201
  1. Start the Shell App:
ng serve shell --port 4200
  1. Open http://localhost:4200/
    βœ…
    Count updates dynamically across Micro Frontends! πŸŽ‰

πŸ”₯ Key Takeaways

βœ… NgRx provides a central store that multiple MFEs can access.
βœ… Closures help encapsulate shared state, ensuring isolation.
βœ… Lazy loading ensures only necessary state is loaded.

Β