Angular 通用 SSR TransferState 只有第一页数据可用
Angular Universal SSR TransferState only first page data available
我已经使用 TransferState API 创建了一个 ApiService 来缓存来自 wordpress 的数据:
get(url, id) {
const key = makeStateKey(id);
if (this.transferState.hasKey(key)) {
const item = this.transferState.get(key, null);
return of(item);
} else {
return this.http.get(url).pipe(
map(items => {
this.transferState.set(key, items);
return items;
})
);
}
}
然后我用它来获取数据:
this.apiService.get(environment.url + '/wp-json/wp/v2/posts').subscribe(res => {
this.posts = res;
});
这很好用,当 运行 第一次调用 API 时,第二次总是被缓存。
静态生成时:
/index.html
<script id="my-app-state" type="application/json">
<!-- top level page data -->
</script>
/posts/index.html
<script id="my-app-state" type="application/json">
<!-- top level page data -->
<!-- posts data -->
</script>
如果我登陆 html 页面 /posts/index.html,数据将从 TransferState 缓存加载
https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/sample-page
如果我登陆 /index.html,并导航到 /posts 它使用 html5 路由,脚本标签中的数据不可用。而是通过 http 加载
https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/
据我了解,原因是因为您登陆了一个真正的静态 index.html 文件,其中包含导航时的 then,所有后续页面都没有加载 .html 文件,它们实际上是 html5条路线/帖子。
所以问题是,如何让 TransferState 缓存使用静态生成的 /posts/index.html 文件?
可能的解决方案:
- 预先加载所有数据(可行,但每个页面为 1.1MB)
- 将数据放入静态文件中,可以通过 ajax
加载
- 禁用 html5 路由,因此用户使用正确的脚本标签访问静态 .html 文件
- 一些未记录的 angular 解决方案??
静态生成的演示:
https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/
静态生成源:
https://github.com/kmturley/angular-universal-wordpress-cms/tree/gh-pages/frontend/dist/browser
完整的代码库在这里:
我设法使用变通方法解决了这个问题。我创建了一个新的 TransferState JSON,其中 return 的数据为 JSON,可以在多个页面之间共享:
export function serializeTransferStateFactory(
doc: Document, appId: string, transferStore: TransferState) {
return () => {
return JSON.parse(transferStore.toJson());
};
}
然后将renderModuleFactory修改为return html和数据分开:
const callbacks = moduleRef.injector.get(BEFORE_APP_SERIALIZED, null);
let data = null;
if (callbacks) {
for (const callback of callbacks) {
try {
data = callback();
} catch (e) {
// Ignore exceptions.
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
}
}
}
const output = platformState.renderToString();
platform.destroy();
return {
output: output,
data: data
};
https://github.com/kmturley/angular-universal-google-apis/blob/master/utils.ts#L53
以便可以将其保存到 prerender.ts 中的 .json 文件中:
previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, {
document: index,
url: route,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
})).then((res: { output: string, data: object }) => {
// write html file
writeFileSync(join(fullPath, 'index.html'), res.output);
// write json files from TransferState objects
Object.keys(res.data).forEach(item => {
writeFileSync(join(jsonPath, item + '.json'), JSON.stringify(res.data[item]));
});
});
https://github.com/kmturley/angular-universal-google-apis/blob/master/prerender.ts#L53
您可以在此处查看使用 json 的演示静态版本:
https://kimturley.co.uk/angular-universal-google-apis/dist/browser/
源代码在这里:
我已经使用 TransferState API 创建了一个 ApiService 来缓存来自 wordpress 的数据:
get(url, id) {
const key = makeStateKey(id);
if (this.transferState.hasKey(key)) {
const item = this.transferState.get(key, null);
return of(item);
} else {
return this.http.get(url).pipe(
map(items => {
this.transferState.set(key, items);
return items;
})
);
}
}
然后我用它来获取数据:
this.apiService.get(environment.url + '/wp-json/wp/v2/posts').subscribe(res => {
this.posts = res;
});
这很好用,当 运行 第一次调用 API 时,第二次总是被缓存。
静态生成时:
/index.html
<script id="my-app-state" type="application/json">
<!-- top level page data -->
</script>
/posts/index.html
<script id="my-app-state" type="application/json">
<!-- top level page data -->
<!-- posts data -->
</script>
如果我登陆 html 页面 /posts/index.html,数据将从 TransferState 缓存加载 https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/sample-page
如果我登陆 /index.html,并导航到 /posts 它使用 html5 路由,脚本标签中的数据不可用。而是通过 http 加载 https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/
据我了解,原因是因为您登陆了一个真正的静态 index.html 文件,其中包含导航时的 then,所有后续页面都没有加载 .html 文件,它们实际上是 html5条路线/帖子。
所以问题是,如何让 TransferState 缓存使用静态生成的 /posts/index.html 文件?
可能的解决方案:
- 预先加载所有数据(可行,但每个页面为 1.1MB)
- 将数据放入静态文件中,可以通过 ajax 加载
- 禁用 html5 路由,因此用户使用正确的脚本标签访问静态 .html 文件
- 一些未记录的 angular 解决方案??
静态生成的演示:
https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/
静态生成源:
https://github.com/kmturley/angular-universal-wordpress-cms/tree/gh-pages/frontend/dist/browser
完整的代码库在这里:
我设法使用变通方法解决了这个问题。我创建了一个新的 TransferState JSON,其中 return 的数据为 JSON,可以在多个页面之间共享:
export function serializeTransferStateFactory(
doc: Document, appId: string, transferStore: TransferState) {
return () => {
return JSON.parse(transferStore.toJson());
};
}
然后将renderModuleFactory修改为return html和数据分开:
const callbacks = moduleRef.injector.get(BEFORE_APP_SERIALIZED, null);
let data = null;
if (callbacks) {
for (const callback of callbacks) {
try {
data = callback();
} catch (e) {
// Ignore exceptions.
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
}
}
}
const output = platformState.renderToString();
platform.destroy();
return {
output: output,
data: data
};
https://github.com/kmturley/angular-universal-google-apis/blob/master/utils.ts#L53
以便可以将其保存到 prerender.ts 中的 .json 文件中:
previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, {
document: index,
url: route,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
})).then((res: { output: string, data: object }) => {
// write html file
writeFileSync(join(fullPath, 'index.html'), res.output);
// write json files from TransferState objects
Object.keys(res.data).forEach(item => {
writeFileSync(join(jsonPath, item + '.json'), JSON.stringify(res.data[item]));
});
});
https://github.com/kmturley/angular-universal-google-apis/blob/master/prerender.ts#L53
您可以在此处查看使用 json 的演示静态版本:
https://kimturley.co.uk/angular-universal-google-apis/dist/browser/
源代码在这里: