@@ -359,8 +359,8 @@ export function triggerDeferBlock(triggerType: TriggerType, lView: LView, tNode:
359359}
360360
361361/**
362- * The core mechanism for incremental hydration. This triggers
363- * hydration for all the blocks in the tree that need to be hydrated
362+ * The core mechanism for incremental hydration. This triggers or
363+ * queues hydration for all the blocks in the tree that need to be hydrated
364364 * and keeps track of all those blocks that were hydrated along the way.
365365 *
366366 * Note: the `replayQueuedEventsFn` is only provided when hydration is invoked
@@ -381,31 +381,70 @@ export async function triggerHydrationFromBlockName(
381381 return ;
382382 }
383383
384- // The parent promise is the possible case of a list of defer blocks already being queued
385- // If it is queued, it'll exist; otherwise it'll be null. The hydration queue will contain all
386- // elements that need to be hydrated, sans any that have promises already
384+ // Trigger resource loading and hydration for the blocks in the queue in the order of highest block
385+ // to lowest block. Once a block has finished resource loading, after next render fires after hydration
386+ // finishes. The new block will have its defer instruction called and will be in the registry.
387+ // Due to timing related to potential nested control flow, this has to be scheduled after the next render.
387388 const { parentBlockPromise, hydrationQueue} = getParentBlockHydrationQueue ( blockName , injector ) ;
389+ if ( hydrationQueue . length === 0 ) return ;
390+
391+ // It's possible that the hydrationQueue topmost item is actually in the process of hydrating and has
392+ // a promise already. In that case, we don't want to destroy that promise and queue it again.
393+ if ( parentBlockPromise !== null ) {
394+ hydrationQueue . shift ( ) ;
395+ }
388396
389397 // The hydrating map in the registry prevents re-triggering hydration for a block that's already in
390398 // the hydration queue. Here we generate promises for each of the blocks about to be hydrated
391399 populateHydratingStateForQueue ( dehydratedBlockRegistry , hydrationQueue ) ;
392400
393- // Trigger resource loading and hydration for the blocks in the queue in the order of highest block
394- // to lowest block. Once a block has finished resource loading, after next render fires after hydration
395- // finishes. The new block will have its defer instruction called and will be in the registry.
396- // Due to timing related to potential nested control flow, this has to be scheduled after the next render.
401+ // We await this after populating the hydration state so we can prevent re-triggering hydration for
402+ // the same blocks while this promise is being awaited.
403+ if ( parentBlockPromise !== null ) {
404+ await parentBlockPromise ;
405+ }
406+
407+ const topmostParentBlock = hydrationQueue [ 0 ] ;
408+ if ( dehydratedBlockRegistry . has ( topmostParentBlock ) ) {
409+ // the topmost parent block is already in the registry and we can proceed
410+ // with hydration.
411+ await triggerHydrationForBlockQueue ( injector , hydrationQueue , replayQueuedEventsFn ) ;
412+ } else {
413+ // the topmost parent block is not yet in the registry, which may mean
414+ // a lazy loaded route, a control flow branch was taken, a route has
415+ // been navigated, etc. So we need to queue up the hydration process
416+ // so that it can be finished after the top block has had its defer
417+ // instruction executed.
418+ dehydratedBlockRegistry . awaitParentBlock (
419+ topmostParentBlock ,
420+ async ( ) =>
421+ await triggerHydrationForBlockQueue ( injector , hydrationQueue , replayQueuedEventsFn ) ,
422+ ) ;
423+ }
424+ }
425+
426+ /**
427+ * The core mechanism for incremental hydration. This triggers
428+ * hydration for all the blocks in the tree that need to be hydrated
429+ * and keeps track of all those blocks that were hydrated along the way.
430+ *
431+ * Note: the `replayQueuedEventsFn` is only provided when hydration is invoked
432+ * as a result of an event replay (via JsAction). When hydration is invoked from
433+ * an instruction set (e.g. `deferOnImmediate`) - there is no need to replay any
434+ * events.
435+ */
436+ export async function triggerHydrationForBlockQueue (
437+ injector : Injector ,
438+ hydrationQueue : string [ ] ,
439+ replayQueuedEventsFn ?: Function ,
440+ ) : Promise < void > {
441+ const dehydratedBlockRegistry = injector . get ( DEHYDRATED_BLOCK_REGISTRY ) ;
442+ const blocksBeingHydrated = dehydratedBlockRegistry . hydrating ;
397443
398444 // Indicate that we have some pending async work.
399445 const pendingTasks = injector . get ( PendingTasksInternal ) ;
400446 const taskId = pendingTasks . add ( ) ;
401447
402- // If the parent block was being hydrated, but the process has
403- // not yet complete, wait until parent block promise settles before
404- // going over dehydrated blocks from the queue.
405- if ( parentBlockPromise !== null ) {
406- await parentBlockPromise ;
407- }
408-
409448 // Actually do the triggering and hydration of the queue of blocks
410449 for ( let blockQueueIdx = 0 ; blockQueueIdx < hydrationQueue . length ; blockQueueIdx ++ ) {
411450 const dehydratedBlockId = hydrationQueue [ blockQueueIdx ] ;
@@ -443,8 +482,10 @@ export async function triggerHydrationFromBlockName(
443482 }
444483 }
445484
446- // Await hydration completion for the requested block.
447- await blocksBeingHydrated . get ( blockName ) ?. promise ;
485+ const lastBlockName = hydrationQueue [ hydrationQueue . length - 1 ] ;
486+
487+ // Await hydration completion for the last block.
488+ await blocksBeingHydrated . get ( lastBlockName ) ?. promise ;
448489
449490 // All async work is done, remove the taskId from the registry.
450491 pendingTasks . remove ( taskId ) ;
@@ -456,7 +497,7 @@ export async function triggerHydrationFromBlockName(
456497
457498 // Cleanup after hydration of all affected defer blocks.
458499 cleanupHydratedDeferBlocks (
459- dehydratedBlockRegistry . get ( blockName ) ,
500+ dehydratedBlockRegistry . get ( lastBlockName ) ,
460501 hydrationQueue ,
461502 dehydratedBlockRegistry ,
462503 injector . get ( ApplicationRef ) ,
@@ -683,7 +724,7 @@ function setTimerTriggers(injector: Injector, elementTriggers: ElementTrigger[])
683724function setImmediateTriggers ( injector : Injector , elementTriggers : ElementTrigger [ ] ) {
684725 for ( const elementTrigger of elementTriggers ) {
685726 // Note: we intentionally avoid awaiting each call and instead kick off
686- // th hydration process simultaneously for all defer blocks with this trigger;
727+ // the hydration process simultaneously for all defer blocks with this trigger;
687728 triggerHydrationFromBlockName ( injector , elementTrigger . blockName ) ;
688729 }
689730}
0 commit comments