Search code examples
angularangular-universalstatic-generatorangular-transfer-state

Angular Universal SSR TransferState only first page data available


I've created an ApiService using the TransferState API to cache data from a 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;
      })
    );
  }
}

Then i'm using it to get data:

this.apiService.get(environment.url + '/wp-json/wp/v2/posts').subscribe(res => {
  this.posts = res;
});

This works well, and when run, calls the API the first time, then second time is always cached.

When statically generated:

/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>

From what I understand the reason is because you land on a real static index.html file, containing the then when navigating, all subsequent pages are not loading .html files, they are actually html5 routes /posts.

So the question is, how can I get the TransferState cache to use the from the /posts/index.html file which was statically generated?

Potential solutions:

  • Loading all data upfront (works, but pages are 1.1MB each)
  • Putting data into a static file which can be loaded by ajax
  • disabling html5 routing, so user hits static .html file with correct script tag
  • some undocumented angular solution??

Static generated demo:

https://kmturley.github.io/angular-universal-wordpress-cms/frontend/dist/browser/

Static generated source:

https://github.com/kmturley/angular-universal-wordpress-cms/tree/gh-pages/frontend/dist/browser

Full codebase is here:

https://github.com/kmturley/angular-universal-wordpress-cms


Solution

  • I managed to solve this using a workaround. I created a new TransferState JSON which returns data as JSON, which can be shared between multiple pages:

    export function serializeTransferStateFactory(
        doc: Document, appId: string, transferStore: TransferState) {
      return () => {
        return JSON.parse(transferStore.toJson());
      };
    }
    

    https://github.com/kmturley/angular-universal-google-apis/blob/master/src/app/shared/transfer_state_json.ts

    and then modified the renderModuleFactory to return html and data separately:

    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

    so that it can be saved to a .json file in 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

    You can see a demo static version using the json here:

    https://kimturley.co.uk/angular-universal-google-apis/dist/browser/

    and the source code here:

    https://github.com/kmturley/angular-universal-google-apis