fix(router): update current state and url before activating components
This commit is contained in:
parent
6518ff88b2
commit
5cf58971f1
|
@ -85,7 +85,7 @@ export class RouterLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
get urlTree(): UrlTree {
|
get urlTree(): UrlTree {
|
||||||
return this.router.createUrlTreeUsingFutureUrl(
|
return this.router.createUrlTree(
|
||||||
this.commands,
|
this.commands,
|
||||||
{relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment});
|
{relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment});
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTargetUrlAndHref(): void {
|
private updateTargetUrlAndHref(): void {
|
||||||
this.urlTree = this.router.createUrlTreeUsingFutureUrl(
|
this.urlTree = this.router.createUrlTree(
|
||||||
this.commands,
|
this.commands,
|
||||||
{relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment});
|
{relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment});
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,6 @@ export class Router {
|
||||||
private routerEvents: Subject<Event>;
|
private routerEvents: Subject<Event>;
|
||||||
private navigationId: number = 0;
|
private navigationId: number = 0;
|
||||||
private config: Routes;
|
private config: Routes;
|
||||||
private futureUrlTree: UrlTree;
|
|
||||||
private configLoader: RouterConfigLoader;
|
private configLoader: RouterConfigLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,7 +137,6 @@ export class Router {
|
||||||
this.resetConfig(config);
|
this.resetConfig(config);
|
||||||
this.routerEvents = new Subject<Event>();
|
this.routerEvents = new Subject<Event>();
|
||||||
this.currentUrlTree = createEmptyUrlTree();
|
this.currentUrlTree = createEmptyUrlTree();
|
||||||
this.futureUrlTree = this.currentUrlTree;
|
|
||||||
this.configLoader = new RouterConfigLoader(loader);
|
this.configLoader = new RouterConfigLoader(loader);
|
||||||
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
|
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
|
||||||
}
|
}
|
||||||
|
@ -230,18 +228,6 @@ export class Router {
|
||||||
return createUrlTree(a, this.currentUrlTree, commands, queryParams, fragment);
|
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.
|
* Navigate based on the provided url. This navigation is always absolute.
|
||||||
*
|
*
|
||||||
|
@ -317,18 +303,22 @@ export class Router {
|
||||||
let state: RouterState;
|
let state: RouterState;
|
||||||
let navigationIsSuccessful: boolean;
|
let navigationIsSuccessful: boolean;
|
||||||
let preActivation: PreActivation;
|
let preActivation: PreActivation;
|
||||||
|
|
||||||
|
let appliedUrl: UrlTree;
|
||||||
|
|
||||||
|
const storedState = this.currentRouterState;
|
||||||
|
const storedUrl = this.currentUrlTree;
|
||||||
|
|
||||||
applyRedirects(this.configLoader, url, this.config)
|
applyRedirects(this.configLoader, url, this.config)
|
||||||
.mergeMap(u => {
|
.mergeMap(u => {
|
||||||
this.futureUrlTree = u;
|
appliedUrl = u;
|
||||||
return recognize(
|
return recognize(
|
||||||
this.rootComponentType, this.config, this.futureUrlTree,
|
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
|
||||||
this.serializeUrl(this.futureUrlTree));
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.mergeMap((newRouterStateSnapshot) => {
|
.mergeMap((newRouterStateSnapshot) => {
|
||||||
this.routerEvents.next(new RoutesRecognized(
|
this.routerEvents.next(new RoutesRecognized(
|
||||||
id, this.serializeUrl(url), this.serializeUrl(this.futureUrlTree),
|
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot));
|
||||||
newRouterStateSnapshot));
|
|
||||||
return resolve(this.resolver, newRouterStateSnapshot);
|
return resolve(this.resolver, newRouterStateSnapshot);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -361,12 +351,13 @@ export class Router {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap);
|
this.currentUrlTree = appliedUrl;
|
||||||
|
|
||||||
this.currentUrlTree = this.futureUrlTree;
|
|
||||||
this.currentRouterState = state;
|
this.currentRouterState = state;
|
||||||
|
|
||||||
|
new ActivateRoutes(state, storedState).activate(this.outletMap);
|
||||||
|
|
||||||
if (!preventPushState) {
|
if (!preventPushState) {
|
||||||
let path = this.urlSerializer.serialize(this.futureUrlTree);
|
let path = this.urlSerializer.serialize(appliedUrl);
|
||||||
if (this.location.isCurrentPathEqualTo(path)) {
|
if (this.location.isCurrentPathEqualTo(path)) {
|
||||||
this.location.replaceState(path);
|
this.location.replaceState(path);
|
||||||
} else {
|
} else {
|
||||||
|
@ -377,11 +368,13 @@ export class Router {
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
this.routerEvents.next(new NavigationEnd(
|
this.routerEvents.next(
|
||||||
id, this.serializeUrl(url), this.serializeUrl(this.futureUrlTree)));
|
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
|
||||||
resolvePromise(navigationIsSuccessful);
|
resolvePromise(navigationIsSuccessful);
|
||||||
},
|
},
|
||||||
e => {
|
e => {
|
||||||
|
this.currentRouterState = storedState;
|
||||||
|
this.currentUrlTree = storedUrl;
|
||||||
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
|
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
|
||||||
rejectPromise(e);
|
rejectPromise(e);
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,7 +50,6 @@ describe('Integration', () => {
|
||||||
expect(location.path()).toEqual('/child/simple');
|
expect(location.path()).toEqual('/child/simple');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
|
||||||
it('should update location when navigating',
|
it('should update location when navigating',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, TestComponentBuilder, Location],
|
[Router, TestComponentBuilder, Location],
|
||||||
|
@ -459,6 +458,39 @@ describe('Integration', () => {
|
||||||
expect(cmp.deactivations[1] instanceof BlankCmp).toBe(true);
|
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: '<router-outlet></router-outlet>', 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', () => {
|
describe('data', () => {
|
||||||
class ResolveSix implements Resolve<TeamCmp> {
|
class ResolveSix implements Resolve<TeamCmp> {
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): number { return 6; }
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): number { return 6; }
|
||||||
|
@ -733,8 +765,6 @@ describe('Integration', () => {
|
||||||
addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]);
|
addProviders([{provide: 'alwaysFalse', useValue: (a: any, b: any) => false}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle errors
|
|
||||||
|
|
||||||
it('works',
|
it('works',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, TestComponentBuilder, Location],
|
[Router, TestComponentBuilder, Location],
|
||||||
|
|
Loading…
Reference in New Issue