diff --git a/modules/@angular/router/src/directives/router_link.ts b/modules/@angular/router/src/directives/router_link.ts index 7dcf6c613c..904e44f0ca 100644 --- a/modules/@angular/router/src/directives/router_link.ts +++ b/modules/@angular/router/src/directives/router_link.ts @@ -85,7 +85,7 @@ export class RouterLink { } get urlTree(): UrlTree { - return this.router.createUrlTreeUsingFutureUrl( + return this.router.createUrlTree( this.commands, {relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment}); } @@ -148,7 +148,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy { } private updateTargetUrlAndHref(): void { - this.urlTree = this.router.createUrlTreeUsingFutureUrl( + this.urlTree = this.router.createUrlTree( this.commands, {relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment}); diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index e88bc493b8..058b375e00 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -124,7 +124,6 @@ export class Router { private routerEvents: Subject; private navigationId: number = 0; private config: Routes; - private futureUrlTree: UrlTree; private configLoader: RouterConfigLoader; /** @@ -138,7 +137,6 @@ export class Router { this.resetConfig(config); this.routerEvents = new Subject(); this.currentUrlTree = createEmptyUrlTree(); - this.futureUrlTree = this.currentUrlTree; this.configLoader = new RouterConfigLoader(loader); this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType); } @@ -230,18 +228,6 @@ export class Router { return createUrlTree(a, this.currentUrlTree, commands, queryParams, fragment); } - /** - * Used by RouterLinkWithHref to update HREFs. - * We have to use the futureUrl because we run change detection ind the middle of activation when - * the current url has not been updated yet. - * @internal - */ - createUrlTreeUsingFutureUrl( - commands: any[], {relativeTo, queryParams, fragment}: NavigationExtras = {}): UrlTree { - const a = relativeTo ? relativeTo : this.routerState.root; - return createUrlTree(a, this.futureUrlTree, commands, queryParams, fragment); - } - /** * Navigate based on the provided url. This navigation is always absolute. * @@ -317,18 +303,22 @@ export class Router { let state: RouterState; let navigationIsSuccessful: boolean; let preActivation: PreActivation; + + let appliedUrl: UrlTree; + + const storedState = this.currentRouterState; + const storedUrl = this.currentUrlTree; + applyRedirects(this.configLoader, url, this.config) .mergeMap(u => { - this.futureUrlTree = u; + appliedUrl = u; return recognize( - this.rootComponentType, this.config, this.futureUrlTree, - this.serializeUrl(this.futureUrlTree)); + this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)); }) .mergeMap((newRouterStateSnapshot) => { this.routerEvents.next(new RoutesRecognized( - id, this.serializeUrl(url), this.serializeUrl(this.futureUrlTree), - newRouterStateSnapshot)); + id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot)); return resolve(this.resolver, newRouterStateSnapshot); }) @@ -361,12 +351,13 @@ export class Router { return; } - new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap); - - this.currentUrlTree = this.futureUrlTree; + this.currentUrlTree = appliedUrl; this.currentRouterState = state; + + new ActivateRoutes(state, storedState).activate(this.outletMap); + if (!preventPushState) { - let path = this.urlSerializer.serialize(this.futureUrlTree); + let path = this.urlSerializer.serialize(appliedUrl); if (this.location.isCurrentPathEqualTo(path)) { this.location.replaceState(path); } else { @@ -377,11 +368,13 @@ export class Router { }) .then( () => { - this.routerEvents.next(new NavigationEnd( - id, this.serializeUrl(url), this.serializeUrl(this.futureUrlTree))); + this.routerEvents.next( + new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl))); resolvePromise(navigationIsSuccessful); }, e => { + this.currentRouterState = storedState; + this.currentUrlTree = storedUrl; this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e)); rejectPromise(e); }); diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index a19676a2fe..18d6696c0a 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -50,7 +50,6 @@ describe('Integration', () => { expect(location.path()).toEqual('/child/simple'); }))); - it('should update location when navigating', fakeAsync(inject( [Router, TestComponentBuilder, Location], @@ -459,6 +458,39 @@ describe('Integration', () => { expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true); }))); + it('should update url and router state before activating components', + fakeAsync(inject( + [Router, TestComponentBuilder, Location], + (router: Router, tcb: TestComponentBuilder, location: Location) => { + @Component({selector: 'cmp', template: ''}) + class Cmp { + private path: any; + private url: any; + + constructor(router: Router, route: ActivatedRoute) { + this.path = router.routerState.pathFromRoot(route); + this.url = router.url.toString(); + } + } + + @Component( + {selector: 'root', template: '', precompile: [Cmp]}) + class Root { + } + + const fixture = createRoot(tcb, router, Root); + + router.resetConfig([{path: 'cmp', component: Cmp}]); + + router.navigateByUrl('/cmp'); + advance(fixture); + + const cmp = fixture.debugElement.children[1].componentInstance; + + expect(cmp.url).toBe('/cmp'); + expect(cmp.path.length).toEqual(2); + }))); + describe('data', () => { class ResolveSix implements Resolve { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): number { return 6; } @@ -733,8 +765,6 @@ describe('Integration', () => { addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]); }); - // handle errors - it('works', fakeAsync(inject( [Router, TestComponentBuilder, Location],