Angular Universal SSR TransferState доступны только данные первой страницы
Я создал ApiService, используя TransferState API для кэширования данных из 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, данные в теге script недоступны. и загружается через http вместо https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/
Из того, что я понимаю, причина в том, что вы попадаете в настоящий статический файл index.html, содержащий тогда, когда при навигации все последующие страницы не загружают файлы.html, они фактически являются html5 маршрутами / постами.
Итак, вопрос в том, как я могу заставить кэш TransferState использовать файл из /posts/index.html, который был сгенерирован статически?
Потенциальные решения:
- Загрузка всех данных заранее (работает, но каждая страница имеет размер 1,1 МБ)
- Помещение данных в статический файл, который может быть загружен с помощью ajax
- отключение html5 маршрутизации, поэтому пользователь нажимает на статический.html файл с правильным тегом скрипта
- какое-то недокументированное угловое решение??
Статическая сгенерированная демка:
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
Полная кодовая база здесь:
1 ответ
Мне удалось решить это с помощью обходного пути. Я создал новый TransferState JSON, который возвращает данные в формате JSON, которые могут быть разделены между несколькими страницами:
export function serializeTransferStateFactory(
doc: Document, appId: string, transferStore: TransferState) {
return () => {
return JSON.parse(transferStore.toJson());
};
}
а затем изменил renderModuleFactory, чтобы он возвращал 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
чтобы его можно было сохранить в файл.json в prerender.ts:
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://kmturley.github.io/angular-universal-google-apis
и исходный код здесь: