diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index ea7436666d..5144c250ed 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -773,7 +773,14 @@ export class Router { this.routerState.root.component = this.rootComponentType; } - private getTransition(): NavigationTransition { return this.transitions.value; } + private getTransition(): NavigationTransition { + const transition = this.transitions.value; + // This value needs to be set. Other values such as extractedUrl are set on initial navigation + // but the urlAfterRedirects may not get set if we aren't processing the new URL *and* not + // processing the previous URL. + transition.urlAfterRedirects = this.browserUrlTree; + return transition; + } private setTransition(t: Partial): void { this.transitions.next({...this.getTransition(), ...t}); diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index e5e85e84e1..3766ef7065 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -218,6 +218,39 @@ describe('Integration', () => { }))); }); + + /** + * get/setTransition are private APIs. This test is needed though to guarantee the correct + * values are being used. Related to https://github.com/angular/angular/issues/30340 where + * stale transition data was being used when kicking off a new navigation. + */ + describe('get/setTransition', () => { + it('should provide the most recent NavigationTransition', + fakeAsync(inject([Router, Location], (router: Router, location: SpyLocation) => { + router.resetConfig([ + {path: '', component: SimpleCmp}, {path: 'a', component: SimpleCmp}, + {path: 'b', component: SimpleCmp} + ]); + + const fixture = createRoot(router, RootCmp); + + const initialTransition = (router as any).getTransition(); + + // Confirm initial value + expect(initialTransition.urlAfterRedirects.toString()).toBe('/'); + + + router.navigateByUrl('/a', {replaceUrl: true}); + + tick(); + + // After a navigation, we should see the URL after redirect + const nextTransition = (router as any).getTransition(); + // Confirm initial value + expect(nextTransition.urlAfterRedirects.toString()).toBe('/a'); + }))); + }); + describe('navigation warning', () => { let warnings: string[] = []; @@ -662,6 +695,33 @@ describe('Integration', () => { expect(location.path()).toEqual('/login'); }))); + it('should set browserUrlTree with urlUpdateStrategy="eagar" and false `shouldProcessUrl`', + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = TestBed.createComponent(RootCmp); + advance(fixture); + + router.urlUpdateStrategy = 'eager'; + + router.resetConfig([ + {path: 'team/:id', component: SimpleCmp}, + {path: 'login', component: AbsoluteSimpleLinkCmp} + ]); + + router.navigateByUrl('/team/22'); + advance(fixture, 1); + + expect((router as any).browserUrlTree.toString()).toBe('/team/22'); + + // Force to not process URL changes + router.urlHandlingStrategy.shouldProcessUrl = (url: UrlTree) => false; + + router.navigateByUrl('/login'); + advance(fixture, 1); + + // Do not change locations + expect((router as any).browserUrlTree.toString()).toBe('/team/22'); + }))); + it('should eagerly update URL after redirects are applied with urlUpdateStrategy="eagar"', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { const fixture = TestBed.createComponent(RootCmp);