From 1f287b9ba7a5ceeecefdf0ce049e09563813fc8a Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 6 May 2026 12:37:02 -0700 Subject: [PATCH] refactor(router): Move target RouterState creation before 'blocking' stage This change moves `RouterState` creation to _before_ the `afterPreactivation` step, which is the step that pauses until bootstrap listeners are complete. It is used for 'enabled blocking' initial navigation and destructive hydration. After this stage, activation is expected to be (more or less) synchronous. More importantly than above (since enabled blocking and destructive hydration are essentially deprecated), this also oves the state creation before the view transition creation. These are done to accomodate features in the future that would depend on the RouterState (e.g. ones which need to know which `ActivatedRoute` instances are new and which are reused). These features may include additional async blocks/waits, which should not happen after view transition creation (which freezes the UI until resolved). --- packages/router/src/navigation_transition.ts | 29 ++++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/router/src/navigation_transition.ts b/packages/router/src/navigation_transition.ts index 82e01c207a7..5de1fd419ec 100644 --- a/packages/router/src/navigation_transition.ts +++ b/packages/router/src/navigation_transition.ts @@ -443,6 +443,7 @@ export class NavigationTransitions { // Using switchMap so we cancel executing navigations when a new one comes in switchMap((overallTransitionState) => { + let abortable = true; let completedOrAborted = false; const abortController = new AbortController(); const shouldContinueNavigation = () => { @@ -738,6 +739,20 @@ export class NavigationTransitions { return loaders.length === 0 ? of(t) : from(Promise.all(loaders).then(() => t)); }), + switchMap((t: NavigationTransition) => { + const targetRouterState = createRouterState( + router.routeReuseStrategy, + t.targetSnapshot!, + t.currentRouterState, + ); + this.currentTransition = overallTransitionState = t = {...t, targetRouterState}; + this.currentNavigation.update((nav) => { + nav!.targetRouterState = targetRouterState; + return nav; + }); + return of(t); + }), + switchTap(() => this.afterPreactivation()), // TODO(atscott): Move this into the last block below. @@ -762,17 +777,7 @@ export class NavigationTransitions { take(1), switchMap((t: NavigationTransition) => { - const targetRouterState = createRouterState( - router.routeReuseStrategy, - t.targetSnapshot!, - t.currentRouterState, - ); - this.currentTransition = overallTransitionState = t = {...t, targetRouterState}; - this.currentNavigation.update((nav) => { - nav!.targetRouterState = targetRouterState; - return nav; - }); - + abortable = false; this.events.next(new BeforeActivateRoutes()); const deferred = overallTransitionState.beforeActivateHandler.deferredHandle; return deferred ? from(deferred.then(() => t)) : of(t); @@ -811,7 +816,7 @@ export class NavigationTransitions { takeUntil( abortSignalToObservable(abortController.signal).pipe( // Ignore aborts if we are already completed, canceled, or are in the activation stage (we have targetRouterState) - filter(() => !completedOrAborted && !overallTransitionState.targetRouterState), + filter(() => !completedOrAborted && abortable), tap(() => { this.cancelNavigationTransition( overallTransitionState,