-
Notifications
You must be signed in to change notification settings - Fork 26.5k
Description
Which @angular/* package(s) are relevant/related to the feature request?
common, platform-browser
Description
Following #50117, additional configuration was added to the HTTP transfer cache.
One use-case was mentioned in several replies and other issues about the lack of customization of the cache key but it was not handled in the associated PR so I'm opening a new issue, dedicated to this use case.
When using SSR, it is common to use different urls on the server and the browser to access the same APIs.
It could be only the domain, the path or even the http scheme.
The main reason is to reduce network latency by accessing resources directly from a private network.
With the current HTTP transfer cache implementation, the cache key is computed from the HttpRequest
with the makeCacheKey
function.
This function uses the request url among other things so when the url is different on the server and on the browser, the cached response will never be reused from the transfer cache during hydration.
Related comments / issues:
#50117 (comment)
#50117 (comment)
#50117 (comment)
#50117 (comment)
angular/universal#1934
First proposed solution: adding more options to HttpRequest and provideClientHydration()
To override the cache key at the request level, we could provide a custom cache key as a request option.
const customCacheKey = makeCustomCacheKey(url, params);
this.http.get(url, { transferCache: { cacheKey: customCacheKey } });
To globally customize the cache key, we could either use an interceptor (Ability to override transferCache property when cloning the request is needed too)
function customCacheKeyInterceptor = (req, next) => {
const newRequest = req.clone({
transferCache: {
cacheKey: customCacheKey(req) // Custom logic possibly using DI to compute the cache key
...req.transferCache,
}
});
return next(newRequest);
}
Or we could add a "custom cache key function" option to provideClientHydration()
:
provideClientHydration({
customCacheKeyFn: myCustomCacheKeyFn
});
I think that providing a custom cache key function could simplify the usage of and totally replace the existing options includeHeaders
, filter
and includePostRequests
. (but that would be breaking)
These options would probably need to be mutually exclusive because they could overlap.
Another useful thing would be to have access to the current makeCacheKey
function so we could simply override some parts of the http request to customize the cache key:
import { makeCacheKey } from '@angular/common/http';
function customCacheKey(request: HttpRequest) {
// If only the url is different between server and browser, we can only override this part
const overridenRequest = request.clone({ url: 'urlToUseInCacheKey' });
return makeCacheKey(overridenRequest);
}
function customCacheKeyInterceptor = (req, next) => {
const newRequest = req.clone({
transferCache: {
cacheKey: customCacheKey(request)
...req.transferCache,
}
});
return next(newRequest);
}
Second proposed solution: expose transferCacheInterceptorFn
and let developers use it where needed
Another way to solve this (closest to the original behavior with TransferHttpCacheModule
) would be to expose the transferCacheInterceptorFn
in the public API.
We could place this interceptor exactly where we need it in the interceptors chain to control what the request url looks like when cached.
Caching would be enabled by using the interceptor function in provideHttpClient()
and calling provideClientHydration()
.
provideHttpClient(
withInterceptors([
authInterceptorFn, // Add Auth header
transferCacheInterceptorFn, // Cache requests when they are still undifferentiated between browser & server
overrideUrlInterceptorFn, // Custom logic to override the url on server, specific to the user's use case
])),
provideClientHydration(
withNoHttpTransferCache(), // disable injecting transferCacheInterceptorFn as the last interceptor
withHttpTransferCacheOptions({ includePostRequests: true }) // optionally provide global options for transferCacheInterceptorFn
),
Because the interceptor would still rely on CACHE_OPTIONS
which is only provided by provideClientHydration()
, we could make sure that the interceptor is used with provideClientHydration()
and print a warning/error otherwise.
This approach would require minimal changes to the current implementation without adding any breaking change:
- Always provide
CACHE_OPTIONS
even when usingwithNoHttpTransferCache()
, - Provide
transferCacheInterceptorFn
asHTTP_ROOT_INTERCEPTOR_FNS
only whenwithNoHttpTransferCache()
is not used, - Remove the current error thrown when both
withNoHttpTransferCache()
andwithHttpTransferCacheOptions()
are used together.
I believe that this second approach is the best one because it keeps a simple API for simple cases while giving the most flexibility for more advanced use cases. And all of that without any breaking change.
I opened a PR to show how little changes would be needed to implement it.
Alternatives considered
The only alternative is to disable transfer cache from provideClientHydration and rely on a custom caching logic based on an interceptor (like the old TransferHttpCacheModule
from @angular/universal.