Skip to content

Commit f1ce760

Browse files
vsavkinkara
authored andcommitted
fix(router): canLoad should cancel a navigation instead of failing it (angular#11001)
1 parent 7dfcaac commit f1ce760

File tree

5 files changed

+31
-11
lines changed

5 files changed

+31
-11
lines changed

modules/@angular/router/src/apply_redirects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {EmptyError} from 'rxjs/util/EmptyError';
1919

2020
import {Route, Routes} from './config';
2121
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
22-
import {PRIMARY_OUTLET} from './shared';
22+
import {NavigationCancelingError, PRIMARY_OUTLET} from './shared';
2323
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
2424
import {andObservables, merge, waitForMap, wrapIntoObservable} from './utils/collection';
2525

@@ -43,7 +43,7 @@ function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
4343

4444
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
4545
return new Observable<LoadedRouterConfig>(
46-
(obs: Observer<LoadedRouterConfig>) => obs.error(new Error(
46+
(obs: Observer<LoadedRouterConfig>) => obs.error(new NavigationCancelingError(
4747
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
4848
}
4949

modules/@angular/router/src/router.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {recognize} from './recognize';
2929
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
3030
import {RouterOutletMap} from './router_outlet_map';
3131
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
32-
import {PRIMARY_OUTLET, Params} from './shared';
32+
import {NavigationCancelingError, PRIMARY_OUTLET, Params} from './shared';
3333
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
3434
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
3535
import {TreeNode} from './utils/tree';
@@ -162,7 +162,7 @@ export class NavigationEnd {
162162
* @stable
163163
*/
164164
export class NavigationCancel {
165-
constructor(public id: number, public url: string) {}
165+
constructor(public id: number, public url: string, public reason: string) {}
166166

167167
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
168168
}
@@ -440,7 +440,9 @@ export class Router {
440440
id: number): Promise<boolean> {
441441
if (id !== this.navigationId) {
442442
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
443-
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url)));
443+
this.routerEvents.next(new NavigationCancel(
444+
id, this.serializeUrl(url),
445+
`Navigation ID ${id} is not equal to the current navigation id ${this.navigationId}`));
444446
return Promise.resolve(false);
445447
}
446448

@@ -518,15 +520,22 @@ export class Router {
518520
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
519521
resolvePromise(true);
520522
} else {
521-
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url)));
523+
this.routerEvents.next(new NavigationCancel(id, this.serializeUrl(url), ''));
522524
resolvePromise(false);
523525
}
524526
},
525527
e => {
528+
if (e instanceof NavigationCancelingError) {
529+
this.navigated = true;
530+
this.routerEvents.next(
531+
new NavigationCancel(id, this.serializeUrl(url), e.message));
532+
resolvePromise(false);
533+
} else {
534+
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
535+
rejectPromise(e);
536+
}
526537
this.currentRouterState = storedState;
527538
this.currentUrlTree = storedUrl;
528-
this.routerEvents.next(new NavigationError(id, this.serializeUrl(url), e));
529-
rejectPromise(e);
530539
});
531540
});
532541
}

modules/@angular/router/src/shared.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ export const PRIMARY_OUTLET = 'primary';
2222
export type Params = {
2323
[key: string]: any
2424
};
25+
26+
export class NavigationCancelingError extends Error {
27+
public stack: any;
28+
constructor(public message: string) {
29+
super(message);
30+
this.stack = (<any>new Error(message)).stack;
31+
}
32+
toString(): string { return this.message; }
33+
}

modules/@angular/router/test/integration.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,13 +1230,14 @@ describe('Integration', () => {
12301230

12311231

12321232
// failed navigation
1233-
router.navigateByUrl('/lazyFalse/loaded').catch(s => {});
1233+
router.navigateByUrl('/lazyFalse/loaded');
12341234
advance(fixture);
12351235

12361236
expect(location.path()).toEqual('/');
12371237

12381238
expectEvents(recordedEvents, [
1239-
[NavigationStart, '/lazyFalse/loaded'], [NavigationError, '/lazyFalse/loaded']
1239+
[NavigationStart, '/lazyFalse/loaded'],
1240+
[NavigationCancel, '/lazyFalse/loaded']
12401241
]);
12411242

12421243
recordedEvents.splice(0);

tools/public_api_guard/router/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>>
8484
/** @stable */
8585
export declare class NavigationCancel {
8686
id: number;
87+
reason: string;
8788
url: string;
88-
constructor(id: number, url: string);
89+
constructor(id: number, url: string, reason: string);
8990
toString(): string;
9091
}
9192

0 commit comments

Comments
 (0)