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 778b748694)
This commit is contained in:
Andrew Scott 2025-12-22 13:43:08 -08:00 committed by Jessica Janiuk
parent 98343ea35e
commit 84adb2fb3b
2 changed files with 13 additions and 7 deletions

View file

@ -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
);
}
}

View file

@ -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 () => {