fix(router): canLoad should cancel a navigation instead of failing it (#11001)
This commit is contained in:
parent
7dfcaac730
commit
f1ce7607a6
@ -19,7 +19,7 @@ import {EmptyError} from 'rxjs/util/EmptyError';
|
|||||||
|
|
||||||
import {Route, Routes} from './config';
|
import {Route, Routes} from './config';
|
||||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||||
import {PRIMARY_OUTLET} from './shared';
|
import {NavigationCancelingError, PRIMARY_OUTLET} from './shared';
|
||||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||||
import {andObservables, merge, waitForMap, wrapIntoObservable} from './utils/collection';
|
import {andObservables, merge, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
|||||||
|
|
||||||
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||||
return new Observable<LoadedRouterConfig>(
|
return new Observable<LoadedRouterConfig>(
|
||||||
(obs: Observer<LoadedRouterConfig>) => obs.error(new Error(
|
(obs: Observer<LoadedRouterConfig>) => obs.error(new NavigationCancelingError(
|
||||||
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
|
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ import {recognize} from './recognize';
|
|||||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||||
import {RouterOutletMap} from './router_outlet_map';
|
import {RouterOutletMap} from './router_outlet_map';
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||||
import {PRIMARY_OUTLET, Params} from './shared';
|
import {NavigationCancelingError, PRIMARY_OUTLET, Params} from './shared';
|
||||||
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
||||||
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||||
import {TreeNode} from './utils/tree';
|
import {TreeNode} from './utils/tree';
|
||||||
@ -162,7 +162,7 @@ export class NavigationEnd {
|
|||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export class NavigationCancel {
|
export class NavigationCancel {
|
||||||
constructor(public id: number, public url: string) {}
|
constructor(public id: number, public url: string, public reason: string) {}
|
||||||
|
|
||||||
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
|
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
|
||||||
}
|
}
|
||||||
@ -440,7 +440,9 @@ export class Router {
|
|||||||
id: number): Promise<boolean> {
|
id: number): Promise<boolean> {
|
||||||
if (id !== this.navigationId) {
|
if (id !== this.navigationId) {
|
||||||
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
|
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
|
||||||
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url)));
|
this.routerEvents.next(new NavigationCancel(
|
||||||
|
id, this.serializeUrl(url),
|
||||||
|
`Navigation ID ${id} is not equal to the current navigation id ${this.navigationId}`));
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,15 +520,22 @@ export class Router {
|
|||||||
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
|
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
|
||||||
resolvePromise(true);
|
resolvePromise(true);
|
||||||
} else {
|
} else {
|
||||||
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url)));
|
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url), ''));
|
||||||
resolvePromise(false);
|
resolvePromise(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
e => {
|
e => {
|
||||||
|
if (e instanceof NavigationCancelingError) {
|
||||||
|
this.navigated = true;
|
||||||
|
this.routerEvents.next(
|
||||||
|
new NavigationCancel(id, this.serializeUrl(url), e.message));
|
||||||
|
resolvePromise(false);
|
||||||
|
} else {
|
||||||
|
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
|
||||||
|
rejectPromise(e);
|
||||||
|
}
|
||||||
this.currentRouterState = storedState;
|
this.currentRouterState = storedState;
|
||||||
this.currentUrlTree = storedUrl;
|
this.currentUrlTree = storedUrl;
|
||||||
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
|
|
||||||
rejectPromise(e);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -22,3 +22,12 @@ export const PRIMARY_OUTLET = 'primary';
|
|||||||
export type Params = {
|
export type Params = {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class NavigationCancelingError extends Error {
|
||||||
|
public stack: any;
|
||||||
|
constructor(public message: string) {
|
||||||
|
super(message);
|
||||||
|
this.stack = (<any>new Error(message)).stack;
|
||||||
|
}
|
||||||
|
toString(): string { return this.message; }
|
||||||
|
}
|
@ -1230,13 +1230,14 @@ describe('Integration', () => {
|
|||||||
|
|
||||||
|
|
||||||
// failed navigation
|
// failed navigation
|
||||||
router.navigateByUrl('/lazyFalse/loaded').catch(s => {});
|
router.navigateByUrl('/lazyFalse/loaded');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/');
|
expect(location.path()).toEqual('/');
|
||||||
|
|
||||||
expectEvents(recordedEvents, [
|
expectEvents(recordedEvents, [
|
||||||
[NavigationStart, '/lazyFalse/loaded'], [NavigationError, '/lazyFalse/loaded']
|
[NavigationStart, '/lazyFalse/loaded'],
|
||||||
|
[NavigationCancel, '/lazyFalse/loaded']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
recordedEvents.splice(0);
|
recordedEvents.splice(0);
|
||||||
|
3
tools/public_api_guard/router/index.d.ts
vendored
3
tools/public_api_guard/router/index.d.ts
vendored
@ -84,8 +84,9 @@ export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>>
|
|||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationCancel {
|
export declare class NavigationCancel {
|
||||||
id: number;
|
id: number;
|
||||||
|
reason: string;
|
||||||
url: string;
|
url: string;
|
||||||
constructor(id: number, url: string);
|
constructor(id: number, url: string, reason: string);
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user