Angular 18+Native Federation:远程应用LocalStorage同步至宿主方案
问题背景
我有一个包含三个独立Angular 18应用的monorepo,每个应用都有专属库。远程应用的库中使用NgRx,单独运行各应用时,数据可正常存储至localStorage。采用@angular-architects/native-federation@18.2.2搭建微前端架构,宿主应用(shell)运行在localhost:4200,App1在localhost:4201,App2在localhost:4202。
当从宿主加载App1时,由于端口不同,localStorage无法更新。需求是各应用保持独立状态,不共享状态,但需要在宿主中为每个应用更新对应的LocalStorage,让远程应用能读取并使用自身的LocalStorage数据。
当前NgRx代码如下:
import { ActionReducer, ActionReducerMap, MetaReducer, provideState, } from '@ngrx/store'; import { localStorageSync } from 'ngrx-store-localstorage'; import { currentTaskReducer } from './reducer/Task.reducer'; import { toolboxReducer } from './reducer/toolbox.reducer'; export const reducers: ActionReducerMap<any> = { currentTask: currentTaskReducer, toolbox: toolboxReducer, }; export function localStorageSyncReducer( reducer: ActionReducer<any>, ): ActionReducer<any> { return localStorageSync({ // add keys to sync with local storage keys: ['currentTask', 'toolbox'], rehydrate: true, })(reducer); } export const metaReducers: Array<MetaReducer<any, any>> = [ localStorageSyncReducer, ];
解决方案
1. 为每个应用添加localStorage专属前缀
核心思路是通过给每个应用的存储键添加唯一前缀,实现数据隔离。修改ngrx-store-localstorage的配置,使用storageKeySerializer自定义存储键格式:
// App1的meta-reducer.ts export function localStorageSyncReducer( reducer: ActionReducer<any>, ): ActionReducer<any> { return localStorageSync({ keys: ['currentTask', 'toolbox'], rehydrate: true, // 为App1添加专属前缀 storageKeySerializer: (key) => `app1_${key}`, })(reducer); }
同理,App2的配置将前缀改为app2_,这样两个应用的localStorage数据会分别以app1_currentTask、app2_toolbox等键存储,完全隔离。
2. 结合Native Federation共享上下文动态设置前缀
如果需要宿主根据加载的应用动态传递标识,可通过Native Federation共享上下文服务实现:
步骤1:宿主配置共享上下文服务
在宿主的federation.config.js中添加共享配置:
module.exports = { name: 'shell', exposes: {}, shared: { '@angular/core': { singleton: true, strictVersion: true }, '@angular/common': { singleton: true, strictVersion: true }, // 共享应用上下文服务 './app-context': { singleton: true, import: () => import('./src/app/app-context.service') }, }, };
步骤2:远程应用导入共享服务
在App1的federation.config.js中配置导入:
module.exports = { name: 'app1', exposes: { './Module': './src/app/app1.module.ts', }, shared: { '@angular/core': { singleton: true, strictVersion: true }, '@angular/common': { singleton: true, strictVersion: true }, './app-context': { singleton: true, import: () => import('shell/app-context') }, }, };
步骤3:实现上下文服务
宿主中创建上下文服务,用于传递当前加载的应用标识:
// src/app/app-context.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AppContextService { currentAppId: string = ''; setCurrentAppId(appId: string) { this.currentAppId = appId; } }
步骤4:远程应用动态获取前缀
在App1的meta-reducer中,注入上下文服务动态生成存储键:
// App1的meta-reducer.ts import { inject } from '@angular/core'; import { ActionReducer } from '@ngrx/store'; import { localStorageSync } from 'ngrx-store-localstorage'; import { AppContextService } from 'shell/app-context'; export function localStorageSyncReducer( reducer: ActionReducer<any>, ): ActionReducer<any> { const appContext = inject(AppContextService); return localStorageSync({ keys: ['currentTask', 'toolbox'], rehydrate: true, storageKeySerializer: (key) => `${appContext.currentAppId}_${key}`, })(reducer); }
3. 验证效果
- 单独运行App1时,localStorage会生成
app1_currentTask等键; - 宿主加载App1时,远程应用会读取对应前缀的键,确保状态独立且能正确读写自身数据;
- App2的配置同理,数据完全隔离。
内容的提问来源于stack exchange,提问作者vsam

