From 84adb2fb3b3c0fa33f2dce44736d0ce130c44349 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 22 Dec 2025 13:43:08 -0800 Subject: [PATCH] refactor(router): Permit deferring commit of traversal navigations This updates the state manager to allow intercepting and deferring commits of traversal navigations. The issues that were encountered in the past appear to be resolved in Chrome. The behavior of redirect is still undefined in this case, so there is an added TODO. (cherry picked from commit 778b7486942eecb3a88434ecf27bc86e11f1a3f0) --- .../src/statemanager/navigation_state_manager.ts | 11 +++++++---- .../router/test/computed_state_restoration.spec.ts | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/router/src/statemanager/navigation_state_manager.ts b/packages/router/src/statemanager/navigation_state_manager.ts index e83ebe19c3e..f7de8a7768b 100644 --- a/packages/router/src/statemanager/navigation_state_manager.ts +++ b/packages/router/src/statemanager/navigation_state_manager.ts @@ -463,7 +463,12 @@ export class NavigationStateManager extends StateManager { >((resolve) => { // The `precommitHandler` option is not in the standard DOM types yet (interceptOptions as any).precommitHandler = (controller: any) => { - resolve(controller.redirect.bind(controller)); + if (this.navigation.transition?.navigationType === 'traverse') { + // TODO(atscott): Figure out correct behavior for redirecting traversals + resolve(() => {}); + } else { + resolve(controller.redirect.bind(controller)); + } return precommitHandlerPromise; }; }); @@ -543,9 +548,7 @@ export class NavigationStateManager extends StateManager { return ( this.precommitHandlerSupported && // Cannot defer commit if not cancelable by the Navigation API's rules. - event.cancelable && - // Deferring a traversal commit is currently problematic or not fully supported. - event.navigationType !== 'traverse' + event.cancelable ); } } diff --git a/packages/router/test/computed_state_restoration.spec.ts b/packages/router/test/computed_state_restoration.spec.ts index 2dead2978d4..f451a623745 100644 --- a/packages/router/test/computed_state_restoration.spec.ts +++ b/packages/router/test/computed_state_restoration.spec.ts @@ -330,14 +330,17 @@ for (const browserAPI of ['navigation', 'history'] as const) { location.back(); await nextNavigation(); expect(location.path()).toEqual('/unguarded'); - expectPageIndex(2); + // With 'navigation' API, we never commit the transition back to 'second' + // so the "redirect" from the canActivate guard that triggered a new browser + // navigation actually cancels the back traversal from second to first. + expectPageIndex(browserAPI === 'navigation' ? 3 : 2); TestBed.inject(MyCanActivateGuard).redirectTo = null; location.back(); await nextNavigation(); - expect(location.path()).toEqual('/first'); - expectPageIndex(1); + expect(location.path()).toEqual(browserAPI === 'navigation' ? '/second' : '/first'); + expectPageIndex(browserAPI === 'navigation' ? 2 : 1); }); it('restores history correctly when component throws error in constructor and replaceUrl=true', async () => {