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


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
- Start the Remote MFE1:
ng serve mfe1 --port 4201
- Start the Shell App:
ng serve shell --port 4200
- 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.
Subscribe to my newsletter
Read articles from Rohit Bhat directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
