Photo by Hunter Starritt on Unsplash
π οΈ 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.
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.