mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(router): Add stable cancelation code to NavigationCancel event (#46675)
This commit adds a stable cancelation code to the `NavigationCancel` event. This code is acceptable for use in production whereas parsing the `reason` string is not. This allows developers to determine more specifically _why_ a navigation was canceled and perform different actions in different scenarios. PR Close #46675
This commit is contained in:
committed by
Andrew Scott
parent
e96ece901c
commit
6c1357dd7d
@@ -351,8 +351,9 @@ export class NavigationCancel extends RouterEvent {
|
||||
constructor(
|
||||
id: number,
|
||||
url: string,
|
||||
reason: string);
|
||||
// (undocumented)
|
||||
reason: string,
|
||||
code?: NavigationCancellationCode | undefined);
|
||||
readonly code?: NavigationCancellationCode | undefined;
|
||||
reason: string;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
@@ -360,6 +361,14 @@ export class NavigationCancel extends RouterEvent {
|
||||
readonly type = EventType.NavigationCancel;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const enum NavigationCancellationCode {
|
||||
GuardRejected = 3,
|
||||
NoDataFromResolver = 2,
|
||||
Redirect = 0,
|
||||
SupersededByNewNavigation = 1
|
||||
}
|
||||
|
||||
// @public
|
||||
export class NavigationEnd extends RouterEvent {
|
||||
constructor(
|
||||
|
||||
@@ -162,6 +162,31 @@ export class NavigationEnd extends RouterEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A code for the `NavigationCancel` event of the `Router` to indicate the
|
||||
* reason a navigation failed.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export const enum NavigationCancellationCode {
|
||||
/**
|
||||
* A navigation failed because a guard returned a `UrlTree` to redirect.
|
||||
*/
|
||||
Redirect,
|
||||
/**
|
||||
* A navigation failed because a more recent navigation started.
|
||||
*/
|
||||
SupersededByNewNavigation,
|
||||
/**
|
||||
* A navigation failed because one of the resolvers completed without emiting a value.
|
||||
*/
|
||||
NoDataFromResolver,
|
||||
/**
|
||||
* A navigation failed because a guard returned `false`.
|
||||
*/
|
||||
GuardRejected,
|
||||
}
|
||||
|
||||
/**
|
||||
* An event triggered when a navigation is canceled, directly or indirectly.
|
||||
* This can happen for several reasons including when a route guard
|
||||
@@ -181,8 +206,17 @@ export class NavigationCancel extends RouterEvent {
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public reason: string) {
|
||||
/**
|
||||
* A description of why the navigation was cancelled. For debug purposes only. Use `code`
|
||||
* instead for a stable cancellation reason that can be used in production.
|
||||
*/
|
||||
public reason: string,
|
||||
/**
|
||||
* A code to indicate why the navigation was canceled. This cancellation code is stable for
|
||||
* the reason and can be relied on whereas the `reason` string could change and should not be
|
||||
* used in production.
|
||||
*/
|
||||
readonly code?: NavigationCancellationCode) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export {createUrlTreeFromSnapshot} from './create_url_tree';
|
||||
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
export {RouterLinkActive} from './directives/router_link_active';
|
||||
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
|
||||
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
|
||||
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode as NavigationCancellationCode, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
|
||||
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, CanMatch, CanMatchFn, Data, LoadChildren, LoadChildrenCallback, QueryParamsHandling, Resolve, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
|
||||
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
|
||||
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
|
||||
@@ -14,7 +14,7 @@ import {catchError, defaultIfEmpty, filter, finalize, map, switchMap, take, tap}
|
||||
import {createRouterState} from './create_router_state';
|
||||
import {createUrlTree} from './create_url_tree';
|
||||
import {RuntimeErrorCode} from './errors';
|
||||
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
||||
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
||||
import {QueryParamsHandling, Route, Routes} from './models';
|
||||
import {activateRoutes} from './operators/activate_routes';
|
||||
import {applyRedirects} from './operators/apply_redirects';
|
||||
@@ -411,8 +411,7 @@ export class Router {
|
||||
private disposed = false;
|
||||
|
||||
private locationSubscription?: SubscriptionLike;
|
||||
/** @internal */
|
||||
navigationId: number = 0;
|
||||
private navigationId: number = 0;
|
||||
|
||||
/**
|
||||
* The id of the currently active page in the router.
|
||||
@@ -779,7 +778,8 @@ export class Router {
|
||||
filter(t => {
|
||||
if (!t.guardsResult) {
|
||||
this.restoreHistory(t);
|
||||
this.cancelNavigationTransition(t, '');
|
||||
this.cancelNavigationTransition(
|
||||
t, '', NavigationCancellationCode.GuardRejected);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -807,7 +807,10 @@ export class Router {
|
||||
this.restoreHistory(t);
|
||||
this.cancelNavigationTransition(
|
||||
t,
|
||||
`At least one route resolver didn't emit any value.`);
|
||||
NG_DEV_MODE ?
|
||||
`At least one route resolver didn't emit any value.` :
|
||||
'',
|
||||
NavigationCancellationCode.NoDataFromResolver);
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -896,9 +899,13 @@ export class Router {
|
||||
* event is fired when a navigation gets cancelled but not caught by other
|
||||
* means. */
|
||||
if (!completed && !errored) {
|
||||
const cancelationReason = `Navigation ID ${
|
||||
t.id} is not equal to the current navigation id ${this.navigationId}`;
|
||||
this.cancelNavigationTransition(t, cancelationReason);
|
||||
const cancelationReason = NG_DEV_MODE ?
|
||||
`Navigation ID ${t.id} is not equal to the current navigation id ${
|
||||
this.navigationId}` :
|
||||
'';
|
||||
this.cancelNavigationTransition(
|
||||
t, cancelationReason,
|
||||
NavigationCancellationCode.SupersededByNewNavigation);
|
||||
}
|
||||
// Only clear current navigation if it is still set to the one that
|
||||
// finalized.
|
||||
@@ -933,7 +940,8 @@ export class Router {
|
||||
this.restoreHistory(t, true);
|
||||
}
|
||||
const navCancel = new NavigationCancel(
|
||||
t.id, this.serializeUrl(t.extractedUrl), e.message);
|
||||
t.id, this.serializeUrl(t.extractedUrl), e.message,
|
||||
NavigationCancellationCode.Redirect);
|
||||
eventsSubject.next(navCancel);
|
||||
|
||||
// When redirecting, we need to delay resolving the navigation
|
||||
@@ -1460,8 +1468,9 @@ export class Router {
|
||||
this.generateNgRouterState(this.lastSuccessfulId, this.currentPageId));
|
||||
}
|
||||
|
||||
private cancelNavigationTransition(t: NavigationTransition, reason: string) {
|
||||
const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), reason);
|
||||
private cancelNavigationTransition(
|
||||
t: NavigationTransition, reason: string, code: NavigationCancellationCode) {
|
||||
const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), reason, code);
|
||||
this.triggerEvent(navCancel);
|
||||
t.resolve(false);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
import {RouterLinkActive} from './directives/router_link_active';
|
||||
import {RouterOutlet} from './directives/router_outlet';
|
||||
import {RuntimeErrorCode} from './errors';
|
||||
import {Event, NavigationCancel, NavigationEnd, NavigationError, stringifyEvent} from './events';
|
||||
import {Event, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, stringifyEvent} from './events';
|
||||
import {Route, Routes} from './models';
|
||||
import {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
|
||||
import {RouteReuseStrategy} from './route_reuse_strategy';
|
||||
@@ -26,7 +26,6 @@ import {ChildrenOutletContexts} from './router_outlet_context';
|
||||
import {PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||
import {ROUTER_SCROLLER, RouterScroller} from './router_scroller';
|
||||
import {ActivatedRoute} from './router_state';
|
||||
import {REDIRECTING_CANCELLATION_REASON} from './shared';
|
||||
import {UrlHandlingStrategy} from './url_handling_strategy';
|
||||
import {DefaultUrlSerializer, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {flatten} from './utils/collection';
|
||||
@@ -611,15 +610,11 @@ function provideEnabledBlockingInitialNavigation(): Provider {
|
||||
// Navigation assumed to succeed if we get `ActivationStart`
|
||||
return true;
|
||||
}
|
||||
const newNavigationStarted = router.navigationId !== e.id;
|
||||
// TODO(atscott): Do not rely on the string reason to determine if cancelation
|
||||
// is redirecting
|
||||
const redirectingWithUrlTree = e instanceof NavigationCancel ?
|
||||
e.reason.indexOf(REDIRECTING_CANCELLATION_REASON) !== -1 :
|
||||
const redirecting = e instanceof NavigationCancel ?
|
||||
(e.code === NavigationCancellationCode.Redirect ||
|
||||
e.code === NavigationCancellationCode.SupersededByNewNavigation) :
|
||||
false;
|
||||
// Navigation failed, but if we already have a new navigation, wait for the
|
||||
// result of that one instead.
|
||||
return newNavigationStarted || redirectingWithUrlTree ? null : false;
|
||||
return redirecting ? null : false;
|
||||
}),
|
||||
filter((result): result is boolean => result !== null),
|
||||
take(1),
|
||||
|
||||
Reference in New Issue
Block a user