Skip to content

Commit e8ae0fe

Browse files
atscottthePunderWoman
authored andcommitted
fix(router): Fix cancellation code for canLoad rejections (#46752)
Before this commit, the `NavigationCancellationCode` would always be set to `Redirect` when encountering a "navigationcancelingError". However, this error can also be thrown when `CanLoad` guars reject. This commit ensures these cancellation errors have a code as well so this mistake cannot be made again. PR Close #46752
1 parent a4b64bc commit e8ae0fe

File tree

5 files changed

+36
-16
lines changed

5 files changed

+36
-16
lines changed

packages/router/src/apply_redirects.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {EmptyError, from, Observable, of, throwError} from 'rxjs';
1111
import {catchError, concatMap, first, last, map, mergeMap, scan, switchMap, tap} from 'rxjs/operators';
1212

1313
import {RuntimeErrorCode} from './errors';
14+
import {NavigationCancellationCode} from './events';
1415
import {LoadedRouterConfig, Route, Routes} from './models';
1516
import {runCanLoadGuards} from './operators/check_guards';
1617
import {RouterConfigLoader} from './router_config_loader';
@@ -52,8 +53,9 @@ function namedOutletsRedirect(redirectTo: string): Observable<any> {
5253
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
5354
return throwError(navigationCancelingError(
5455
NG_DEV_MODE &&
55-
`Cannot load children because the guard of the route "path: '${
56-
route.path}'" returned false`));
56+
`Cannot load children because the guard of the route "path: '${
57+
route.path}'" returned false`,
58+
NavigationCancellationCode.GuardRejected));
5759
}
5860

5961
/**

packages/router/src/operators/check_guards.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {EnvironmentInjector, Injector} from '@angular/core';
1010
import {concat, defer, from, MonoTypeOperatorFunction, Observable, of, OperatorFunction, pipe} from 'rxjs';
1111
import {concatMap, first, map, mergeMap, tap} from 'rxjs/operators';
1212

13-
import {ActivationStart, ChildActivationStart, Event} from '../events';
13+
import {ActivationStart, ChildActivationStart, Event, NavigationCancellationCode} from '../events';
1414
import {CanLoad, CanLoadFn, CanMatch, CanMatchFn, Route} from '../models';
1515
import {NavigationTransition} from '../router';
1616
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../router_state';
@@ -189,8 +189,8 @@ function redirectIfUrlTree(urlSerializer: UrlSerializer):
189189
if (!isUrlTree(result)) return;
190190

191191
const error: Error&{url?: UrlTree} = navigationCancelingError(
192-
REDIRECTING_CANCELLATION_REASON + urlSerializer.serialize(result));
193-
error.url = result;
192+
REDIRECTING_CANCELLATION_REASON + urlSerializer.serialize(result),
193+
NavigationCancellationCode.Redirect, result);
194194
throw error;
195195
}),
196196
map(result => result === true),

packages/router/src/router.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -761,11 +761,11 @@ export class Router {
761761
checkGuards(this.ngModule.injector, (evt: Event) => this.triggerEvent(evt)),
762762
tap(t => {
763763
if (isUrlTree(t.guardsResult)) {
764-
const error: Error&{url?: UrlTree} = navigationCancelingError(
765-
REDIRECTING_CANCELLATION_REASON +
766-
`"${this.serializeUrl(t.guardsResult)}"`);
767-
error.url = t.guardsResult;
768-
throw error;
764+
throw navigationCancelingError(
765+
NG_DEV_MODE &&
766+
REDIRECTING_CANCELLATION_REASON +
767+
`"${this.serializeUrl(t.guardsResult)}"`,
768+
NavigationCancellationCode.Redirect, t.guardsResult);
769769
}
770770

771771
const guardsEnd = new GuardsCheckEnd(
@@ -941,12 +941,12 @@ export class Router {
941941
}
942942
const navCancel = new NavigationCancel(
943943
t.id, this.serializeUrl(t.extractedUrl), e.message,
944-
NavigationCancellationCode.Redirect);
944+
e.cancellationCode);
945945
eventsSubject.next(navCancel);
946946

947947
// When redirecting, we need to delay resolving the navigation
948948
// promise and push it to the redirect navigation
949-
if (!redirecting) {
949+
if (!isUrlTree(e.url)) {
950950
t.resolve(false);
951951
} else {
952952
const mergedTree =

packages/router/src/shared.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {NavigationCancellationCode} from './events';
910
import {Route, UrlMatchResult} from './models';
10-
import {UrlSegment, UrlSegmentGroup} from './url_tree';
11+
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
1112

1213

1314
/**
@@ -114,13 +115,17 @@ export function convertToParamMap(params: Params): ParamMap {
114115
export const REDIRECTING_CANCELLATION_REASON = 'Redirecting to ';
115116
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
116117

117-
export function navigationCancelingError(message: string|null|false) {
118+
export function navigationCancelingError(
119+
message: string|null|false, code: NavigationCancellationCode, redirectUrl?: UrlTree) {
118120
const error = Error('NavigationCancelingError: ' + (message || ''));
119121
(error as any)[NAVIGATION_CANCELING_ERROR] = true;
122+
(error as any).cancellationCode = code;
123+
(error as any).url = redirectUrl;
120124
return error;
121125
}
122126

123-
export function isNavigationCancelingError(error: Error) {
127+
export function isNavigationCancelingError(error: Error): error is Error&
128+
{cancellationCode: NavigationCancellationCode, url?: UrlTree} {
124129
return error && (error as any)[NAVIGATION_CANCELING_ERROR];
125130
}
126131

packages/router/test/integration.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {ChangeDetectionStrategy, Component, EventEmitter, Inject, Injectable, In
1212
import {ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
1313
import {By} from '@angular/platform-browser/src/dom/debug/by';
1414
import {expect} from '@angular/platform-browser/testing/src/matchers';
15-
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DefaultUrlSerializer, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, Navigation, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ParamMap, Params, PreloadAllModules, PreloadingStrategy, PRIMARY_OUTLET, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouteReuseStrategy, RouterEvent, RouterLink, RouterLinkWithHref, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
15+
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DefaultUrlSerializer, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, Navigation, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationStart, ParamMap, Params, PreloadAllModules, PreloadingStrategy, PRIMARY_OUTLET, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouteReuseStrategy, RouterEvent, RouterLink, RouterLinkWithHref, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
1616
import {EMPTY, Observable, Observer, of, Subscription} from 'rxjs';
1717
import {delay, filter, first, map, mapTo, tap} from 'rxjs/operators';
1818

@@ -1769,6 +1769,7 @@ describe('Integration', () => {
17691769
let locationUrlBeforeEmittingError = '';
17701770
router.events.forEach(e => {
17711771
if (e instanceof NavigationCancel) {
1772+
expect(e.code).toBe(NavigationCancellationCode.GuardRejected);
17721773
routerUrlBeforeEmittingError = router.url;
17731774
locationUrlBeforeEmittingError = location.path();
17741775
}
@@ -2126,6 +2127,9 @@ describe('Integration', () => {
21262127
[NavigationCancel, '/simple'],
21272128
]);
21282129

2130+
expect((recordedEvents[recordedEvents.length - 1] as NavigationCancel).code)
2131+
.toBe(NavigationCancellationCode.NoDataFromResolver);
2132+
21292133
expect(e).toEqual(null);
21302134
})));
21312135

@@ -4324,6 +4328,9 @@ describe('Integration', () => {
43244328
[NavigationCancel, '/lazyFalse/loaded'],
43254329
]);
43264330

4331+
expect((recordedEvents[1] as NavigationCancel).code)
4332+
.toBe(NavigationCancellationCode.GuardRejected);
4333+
43274334
recordedEvents.splice(0);
43284335

43294336
// successful navigation
@@ -4386,6 +4393,9 @@ describe('Integration', () => {
43864393
[GuardsCheckEnd, '/blank'], [ResolveStart, '/blank'], [ResolveEnd, '/blank'],
43874394
[ActivationEnd], [ChildActivationEnd], [NavigationEnd, '/blank']
43884395
]);
4396+
4397+
expect((recordedEvents[1] as NavigationCancel).code)
4398+
.toBe(NavigationCancellationCode.SupersededByNewNavigation);
43894399
})));
43904400

43914401
it('should support returning UrlTree from within the guard',
@@ -4421,6 +4431,9 @@ describe('Integration', () => {
44214431
[GuardsCheckEnd, '/blank'], [ResolveStart, '/blank'], [ResolveEnd, '/blank'],
44224432
[ActivationEnd], [ChildActivationEnd], [NavigationEnd, '/blank']
44234433
]);
4434+
4435+
expect((recordedEvents[1] as NavigationCancel).code)
4436+
.toBe(NavigationCancellationCode.Redirect);
44244437
})));
44254438

44264439
// Regression where navigateByUrl with false CanLoad no longer resolved `false` value on

0 commit comments

Comments
 (0)