diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 45312f8a68..270ff37414 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -715,9 +715,14 @@ export class Router { /* This error type is issued during Redirect, and is handled as a cancellation * rather than an error. */ if (isNavigationCancelingError(e)) { - this.navigated = true; const redirecting = isUrlTree(e.url); if (!redirecting) { + // Set property only if we're not redirecting. If we landed on a page and + // redirect to `/` route, the new navigation is going to see the `/` isn't + // a change from the default currentUrlTree and won't navigate. This is + // only applicable with initial navigation, so setting `navigated` only when + // not redirecting resolves this scenario. + this.navigated = true; this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl); } const navCancel = diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index aa8dbbdc60..df22c282c2 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -2256,11 +2256,18 @@ describe('Integration', () => { describe('should redirect when guard returns UrlTree', () => { beforeEach(() => TestBed.configureTestingModule({ - providers: [{ - provide: 'returnUrlTree', - useFactory: (router: Router) => () => { return router.parseUrl('/redirected'); }, - deps: [Router] - }] + providers: [ + { + provide: 'returnUrlTree', + useFactory: (router: Router) => () => { return router.parseUrl('/redirected'); }, + deps: [Router] + }, + { + provide: 'returnRootUrlTree', + useFactory: (router: Router) => () => { return router.parseUrl('/'); }, + deps: [Router] + } + ] })); it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => { @@ -2305,6 +2312,49 @@ describe('Integration', () => { [NavigationEnd, '/redirected'], ]); }))); + + it('works with root url', + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const recordedEvents: any[] = []; + let cancelEvent: NavigationCancel = null !; + router.events.forEach((e: any) => { + recordedEvents.push(e); + if (e instanceof NavigationCancel) cancelEvent = e; + }); + router.resetConfig([ + {path: '', component: SimpleCmp}, + {path: 'one', component: RouteCmp, canActivate: ['returnRootUrlTree']} + ]); + + const fixture = TestBed.createComponent(RootCmp); + router.navigateByUrl('/one'); + + advance(fixture); + + expect(location.path()).toEqual('/'); + expect(fixture.nativeElement).toHaveText('simple'); + expect(cancelEvent && cancelEvent.reason) + .toBe('NavigationCancelingError: Redirecting to "/"'); + expectEvents(recordedEvents, [ + [NavigationStart, '/one'], + [RoutesRecognized, '/one'], + [GuardsCheckStart, '/one'], + [ChildActivationStart, undefined], + [ActivationStart, undefined], + [NavigationCancel, '/one'], + [NavigationStart, '/'], + [RoutesRecognized, '/'], + [GuardsCheckStart, '/'], + [ChildActivationStart, undefined], + [ActivationStart, undefined], + [GuardsCheckEnd, '/'], + [ResolveStart, '/'], + [ResolveEnd, '/'], + [ActivationEnd, undefined], + [ChildActivationEnd, undefined], + [NavigationEnd, '/'], + ]); + }))); }); describe('runGuardsAndResolvers', () => {