我有一个使用服务器端渲染的 Angular 17 应用程序。应用程序的状态是使用 ngrx 进行管理的。
当我访问该页面时,我可以看到该页面是预先渲染的(例如,通过查看页面的源代码),但是一旦页面加载,Angular 似乎就从头开始。
例如,我有一些通过HTTP请求获取的标签。最初我在页面中看到标签,但随后 Angular 启动并清除状态,将标签呈现为空白。然后,它执行 HTTP 请求来获取这些标签并再次显示它们。最终结果是正确的,但是当 Angular 接管时会出现闪烁。根据我的阅读,它应该重用服务器的状态。
在浏览器控制台上,我可以看到以下内容:
Angular hydrated 12 component(s) and 98 node(s), 0 component(s) were skipped. Learn more at https://angular.io/guide/hydration.
这似乎表明 Angular 能够水合我的所有组件。我有 Redux 开发工具,通过检查它们,似乎是存储没有被水合,因为初始状态对应于存储的默认状态,而我希望它从来自服务器。
我需要做什么来保持商店的状态?
我找到了以下解决方案。我在
provideStore
调用中注册了一个元减速器。当在服务器上调用时,它会捕获商店的状态并将其添加到 TransferState
。在客户端,它拦截 INIT
操作并恢复存储在传输状态中的状态。
顾名思义,
TransferState
允许在服务器和客户端之间传输数据。例如,Angular 使用它来将 HTTP 缓存传输到客户端。
元减速器是一个可以包装现有减速器的函数,为您提供一个可以拦截存储中发生的所有操作的点。
通过利用这两种机制,我们能够添加我们希望在客户端中立即可用的商店部分。请注意,您可能不想传输整个存储,因为由于 Angular 已经传输了 HTTP 缓存,因此服务器上请求的任何可缓存数据也将立即可供客户端使用。
provideStore({
router: routerReducer,
}, {
metaReducers: [
reducer => {
const storeStateKey = makeStateKey<string>("storeState");
const platformId = inject(PLATFORM_ID);
const transferState = inject(TransferState);
if (isPlatformServer(platformId)) {
let lastState: any = {};
transferState.set(storeStateKey, lastState);
// Only include the keys that are useful to access immediately
transferState.onSerialize<any>(storeStateKey, () => ({
app: lastState["app"],
topMenu: lastState["topMenu"],
localization: lastState["localization"]
}));
return (state, action) => {
lastState = reducer(state, action);
return lastState;
};
} else {
return (state, action) => {
const next = reducer(state, action);
if (action.type === INIT) {
const initialState = transferState.get<any>(storeStateKey, {});
return { ...next, ...initialState };
}
return next;
};
}
}
]
}),