Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TransferState: Who guarantees that data is already stored in state?

I'm doing SSR with TransferState and wondering who guarantees that when we do

http.get(...).subscribe(data => {
  transferState.set(DATA_KEY, data)
})

data will be stored in transferState? Because http.get is async operation and content could be generate and provided to client without this data.

like image 219
Stepan Suvorov Avatar asked Jan 01 '23 12:01

Stepan Suvorov


1 Answers

Angular Zone guarantees that all async operations (the calls tracked by zone.js ) to be finished before rendering.

Let's take a look at

server.ts

app.get('*', (req, res) => {
  res.render('index', { req });
});   
                      ||
                      \/
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

We can see that all regular routes use the Universal engine to render the html.

res.render method (1) defines default callback. The ngExpressEngine function returns another function with that callback passed as parameter (2). As soon as that callback is triggered express sends the result to the user.

done = done || function (err, str) {
  if (err) return req.next(err);
  self.send(str);
};

Now let's see when that callback will be triggered. As mentioned before we need to look at ngExpressEngine function.

 getFactory(moduleOrFactory, compiler)
   .then(factory => {
       return renderModuleFactory(factory, {
          extraProviders
       });
    })
    .then((html: string) => {
      callback(null, html);
    }, (err) => {
      callback(err);
}); 

It will happen only after the promise(3), returning from renderModuleFactory function, is resolved.

renderModuleFactory function can be found at @angular/platform-server

export function renderModuleFactory<T>(
    moduleFactory: NgModuleFactory<T>,
    options: {document?: string, url?: string, extraProviders?: StaticProvider[]}):
    Promise<string> {
  const platform = _getPlatform(platformServer, options);
  return _render(platform, platform.bootstrapModuleFactory(moduleFactory));
}

You can see above that we actually run Angular application here through platform.bootstrapModuleFactory(moduleFactory) (4)

Inside _render function(5) application waits for bootstrapping to be finished

return moduleRefPromise.then((moduleRef) => {

and after that we can see the key for the answer:

return applicationRef.isStable.pipe((first((isStable: boolean) => isStable)))
        .toPromise()
        .then(() => {

You can see that angular universal looks at ApplicationRef.isStable observable to know when to finish rendering. In simple words isStable on ApplicationRef is triggered when Zone has no microtasks scheduled (7):

if (!zone.hasPendingMicrotasks) {
  try {
    zone.runOutsideAngular(() => zone.onStable.emit(null));

enter image description here

like image 136
yurzui Avatar answered May 06 '23 00:05

yurzui