angular: provideClientHydration does not wait for a waterfall of requests for caching

Which @angular/* package(s) are the source of the bug?

common

Is this a regression?

No

Description

So the way that our application works, we kind of have a waterfall of requests due to needing to visit multiple sources for some information:

  • A) go to CMS to get list of campaigns for guest users
  • B) go to API to fetch information about those campaigns
  • C) go back to CMS to fetch the content for the bonuses associated with those campaigns (interpolation purposes etc)

So you end up with requests like so:

A.....
       B.....
              C......... (fully ready)

Obviously we have caching within our CDN so it’s not TOO slow, but when delivering a response from Universal SSR you kind of get this: appReady: false… true… false… true.

We’ve been using TransferState to cache things like so:

return cmsCache.has(CACHE_KEY) ? cmsCache.get(CACHE_KEY) : cmsService.fetchSomething(...);

We sent that data to the browser and re-hydrated like so:

export const CACHE_STATE_KEY = makeStateKey<any>('api-cache.state');
export const apiCache = new Map<ApiCacheKey | string, any>();

...

// SSR hydration and dehydration of the cache
if (transferState.hasKey(CACHE_STATE_KEY)) {
  const state = transferState.get(CACHE_STATE_KEY, {});
  for (const key of Object.keys(state)) {
    apiCache.set(key, state[key]);
  }
} else {
  transferState.onSerialize(CACHE_STATE_KEY, () => Object.fromEntries(apiCache.entries()));
  apiCache.clear();
}

Now since Angular 16 there’s experimental DOM Hydration and automatic HTTP Caching I thought great, I can delete most of my custom code and just use that.

After testing it, it mostly looked good but I noticed that there are a few API requests being made in both server and client. If I add logging to those, I can see them being made in both places but they never get added to TransferState. After some digging I found the culprits:

https://github.com/angular/angular/blob/main/packages/common/http/src/transfer_cache.ts#L170C1-L172C14

https://github.com/angular/universal/blob/main/modules/common/src/transfer_http.ts#L82-L89

For caching and transferState to be used, the application needs to do everything before the first ready state and can’t be: unready/ready/unready/ready (as more requests/pending tasks are discovered).

Is there any reaason for it to work like this? It can’t be correct because Universal still doesn’t return the response until all of that is 100% settled anyway, so shouldn’t the caching/transferstate also work the same way?

In my opinion, either:

  • The logic for when universal should stop what it’s doing and deliver the response, should be the same as when cache and TransferState stop recording
  • Cache and TransferState shouldn’t stop recording until Universal is fully ready and delivers the response

I’m assuming there’s something I’m missing here for why it works the way it does.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 16.2.1
Node: 16.14.0
Package Manager: yarn 1.22.17
OS: darwin arm64

Angular: 16.2.3
... animations, common, compiler, compiler-cli, core, forms
... localize, platform-browser, platform-browser-dynamic
... platform-server, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1602.1
@angular-devkit/build-angular   16.2.1
@angular-devkit/core            16.2.1
@angular-devkit/schematics      16.2.1
@angular/cdk                    16.2.2
@angular/cli                    16.2.1
@angular/flex-layout            15.0.0-beta.42
@angular/material               16.2.2
@nguniversal/builders           16.2.0
@nguniversal/express-engine     16.2.0
@schematics/angular             16.2.1
rxjs                            7.8.1
typescript                      5.0.4
webpack                         5.88.2
zone.js                         0.13.2

Anything else?

As it stands, the only way for me proceed is to either:

  • Copy/Paste all logic and provide it myself using the private APIs
  • Hack my application to force all 3x requests (A, B, C) to be in a single observable and wait for it, instead of letting universal discover when it’s ready based on what appears in the DOM and what is newly discovered.

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 20 (8 by maintainers)

Most upvoted comments

@intellix, is the request a GET?, the HTTP Cache transfer only caches GET and HEAD requests.