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