Skip to content

Commit 09a42d9

Browse files
atscottthePunderWoman
authored andcommitted
fix(router): canceledNavigationResolution: 'computed' with redirects to the current URL (#49793)
The `canceledNavigationResolution: 'computed'` option does not correctly assign page IDs or restore them when redirects result in navigating to the current URL. This change ensures that the page IDs are still incremented and restored correctly in this scenario. PR Close #49793
1 parent 8235bc9 commit 09a42d9

File tree

2 files changed

+67
-22
lines changed

2 files changed

+67
-22
lines changed

packages/router/src/router.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export class Router {
322322
this.navigationTransitions.setupNavigations(this).subscribe(
323323
t => {
324324
this.lastSuccessfulId = t.id;
325-
this.currentPageId = t.targetPageId;
325+
this.currentPageId = this.browserPageId ?? 0;
326326
},
327327
e => {
328328
this.console.warn(`Unhandled Navigation Error: ${e}`);
@@ -699,13 +699,9 @@ export class Router {
699699
if (restoredState && restoredState.ɵrouterPageId) {
700700
targetPageId = restoredState.ɵrouterPageId;
701701
} else {
702-
// If we're replacing the URL or doing a silent navigation, we do not want to increment the
703-
// page id because we aren't pushing a new entry to history.
704-
if (extras.replaceUrl || extras.skipLocationChange) {
705-
targetPageId = this.browserPageId ?? 0;
706-
} else {
707-
targetPageId = (this.browserPageId ?? 0) + 1;
708-
}
702+
// Otherwise, targetPageId should be the next number in the event of a `pushState`
703+
// navigation.
704+
targetPageId = (this.browserPageId ?? 0) + 1;
709705
}
710706
} else {
711707
// This is unused when `canceledNavigationResolution` is not computed.
@@ -737,13 +733,20 @@ export class Router {
737733
/** @internal */
738734
setBrowserUrl(url: UrlTree, transition: NavigationTransition) {
739735
const path = this.urlSerializer.serialize(url);
740-
const state = {
741-
...transition.extras.state,
742-
...this.generateNgRouterState(transition.id, transition.targetPageId)
743-
};
744736
if (this.location.isCurrentPathEqualTo(path) || !!transition.extras.replaceUrl) {
737+
// replacements do not update the target page
738+
const currentBrowserPageId =
739+
this.canceledNavigationResolution === 'computed' ? this.browserPageId : undefined;
740+
const state = {
741+
...transition.extras.state,
742+
...this.generateNgRouterState(transition.id, currentBrowserPageId)
743+
};
745744
this.location.replaceState(path, '', state);
746745
} else {
746+
const state = {
747+
...transition.extras.state,
748+
...this.generateNgRouterState(transition.id, transition.targetPageId)
749+
};
747750
this.location.go(path, '', state);
748751
}
749752
}
@@ -755,16 +758,9 @@ export class Router {
755758
*/
756759
restoreHistory(transition: NavigationTransition, restoringFromCaughtError = false) {
757760
if (this.canceledNavigationResolution === 'computed') {
758-
const targetPagePosition = this.currentPageId - transition.targetPageId;
759-
// The navigator change the location before triggered the browser event,
760-
// so we need to go back to the current url if the navigation is canceled.
761-
// Also, when navigation gets cancelled while using url update strategy eager, then we need to
762-
// go back. Because, when `urlUpdateStrategy` is `eager`; `setBrowserUrl` method is called
763-
// before any verification.
764-
const browserUrlUpdateOccurred =
765-
(transition.source === 'popstate' || this.urlUpdateStrategy === 'eager' ||
766-
this.currentUrlTree === this.getCurrentNavigation()?.finalUrl);
767-
if (browserUrlUpdateOccurred && targetPagePosition !== 0) {
761+
const currentBrowserPageId = this.browserPageId ?? this.currentPageId;
762+
const targetPagePosition = this.currentPageId - currentBrowserPageId;
763+
if (targetPagePosition !== 0) {
768764
this.location.historyGo(targetPagePosition);
769765
} else if (
770766
this.currentUrlTree === this.getCurrentNavigation()?.finalUrl &&

packages/router/test/computed_state_restoration.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,55 @@ describe('`restoredState#ɵrouterPageId`', () => {
357357
const location = TestBed.inject(Location);
358358
const router = TestBed.inject(Router);
359359
router.urlUpdateStrategy = 'eager';
360+
let allowNavigation = true;
361+
router.resetConfig([
362+
{path: 'initial', children: []},
363+
{path: 'redirectFrom', redirectTo: 'redirectTo'},
364+
{path: 'redirectTo', children: [], canActivate: [() => allowNavigation]},
365+
]);
366+
367+
// already at '2' from the `beforeEach` navigations
368+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
369+
router.navigateByUrl('/initial');
370+
advance(fixture);
371+
expect(location.path()).toEqual('/initial');
372+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
373+
374+
TestBed.inject(MyCanActivateGuard).redirectTo = null;
375+
376+
router.navigateByUrl('redirectTo');
377+
advance(fixture);
378+
expect(location.path()).toEqual('/redirectTo');
379+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
380+
381+
// Navigate to different URL but get redirected to same URL should result in same page id
382+
router.navigateByUrl('redirectFrom');
383+
advance(fixture);
384+
expect(location.path()).toEqual('/redirectTo');
385+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
386+
387+
// Back and forward should have page IDs 1 apart
388+
location.back();
389+
advance(fixture);
390+
expect(location.path()).toEqual('/initial');
391+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
392+
location.forward();
393+
advance(fixture);
394+
expect(location.path()).toEqual('/redirectTo');
395+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
396+
397+
// Rejected navigation after redirect to same URL should have the same page ID
398+
allowNavigation = false;
399+
router.navigateByUrl('redirectFrom');
400+
advance(fixture);
401+
expect(location.path()).toEqual('/redirectTo');
402+
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
403+
}));
404+
405+
it('urlUpdateStrategy="eager", redirectTo with same url, and guard reject', fakeAsync(() => {
406+
const location = TestBed.inject(Location);
407+
const router = TestBed.inject(Router);
408+
router.urlUpdateStrategy = 'eager';
360409

361410
TestBed.inject(MyCanActivateGuard).redirectTo = router.createUrlTree(['unguarded']);
362411
router.navigateByUrl('/third');

0 commit comments

Comments
 (0)