Angular SSR doesn't wait for the Amplify auth to reset the session before rendering and returning the html to the client.
My concrete case is: I have apollo client, which is adding Cognito token to each request:
return {
cache,
link: ApolloLink.from([
setContext(async () => {
const token = await Auth.currentSession()
.then((session) => session.getAccessToken().getJwtToken())
.catch(() => undefined);
if (token) {
return authHeaders(token);
}
return unAuthHeaders();
}),
httpLink.create({
uri: environment.graphqlEndpoint
}),
]),
And Angular simply doesn't wait for the token retrieval when on server side rendered, therefore the HTML returned from server is just app with bunch of loading indicators.
For anyone wondering, I transfer the auth info to the ssr using cookie, similar way Amplify was promoting for the React:
if (isPlatformBrowser(this.platformId)) {
Auth.configure(config);
} else {
Auth.configure({
...config,
...(this.request && {storage: new UniversalStorage({req: this.request})})
});
I have found bunch of the questions here for the similar topic (nothing was for the Cognito in particular, but similar) and none of them had satisfying answer, some recommended to block rendering until the REST API fetches data, but that doesn't feel like a right thing to do regarding UX(showing at least some loading to the user etc.), for similar reasons using Route Resolver feels unacceptable to me. And then all the other suggest using hack with scheduling macro task manually:
const macroTask = Zone.current
.scheduleMacroTask(
`WAITFOR-${Math.random()}`,
() => {
},
{},
() => {
}
);
So I ended up going with the one from Jonathan Gamble, not sure I can link blog posts here, but I wanted to give a credit since it's the most useful and straightforward resource I found on the topic:
import { isObservable, Observable } from 'rxjs';
import { take } from 'rxjs/operators';
declare const Zone: any;
async waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {
if (isObservable(prom)) {
prom = firstValueFrom(prom);
}
const macroTask = Zone.current
.scheduleMacroTask(
`WAITFOR-${Math.random()}`,
() => { },
{},
() => { }
);
return prom.then((p: T) => {
macroTask.invoke();
return p;
});
}
waitFor(Auth.currentSession()
.then((session) => session.getAccessToken().getJwtToken())
.catch(() => undefined)
)
I also played around NgZone.run
and NgZone.runTask
none of which helped.
My expectation would be that there is some straightforward kosher way to instruct Angular what matters before returning the HTML from server, in the form of some function wrapper, similar to one introduced above, or any other native API.
I even updated to Angular v17 since that one seems to made huge improvements for SSR, issue still persists.
So I'm wondering whether there is better solution than using tweaks and hacks nowadays, all the threads I previously found were at least few years old and with Angular team putting great efforts to SSR now...