Skip to content

Commit

Permalink
refactor(router): move around some code to eliminate circular deps
Browse files Browse the repository at this point in the history
This commit eliminates some circular dependencies by moving around
interfaces and type guards.
  • Loading branch information
atscott committed Jul 8, 2022
1 parent d04505a commit 13bfdd5
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 102 deletions.
9 changes: 9 additions & 0 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Expand Up @@ -1475,6 +1475,9 @@
{
"name": "isNameOnlyAttributeMarker"
},
{
"name": "isNavigationCancelingError"
},
{
"name": "isNodeMatchingSelector"
},
Expand All @@ -1496,6 +1499,9 @@
{
"name": "isPromise2"
},
{
"name": "isRedirectingNavigationCancelingError"
},
{
"name": "isScheduler"
},
Expand Down Expand Up @@ -1700,6 +1706,9 @@
{
"name": "redirectIfUrlTree"
},
{
"name": "redirectingNavigationError"
},
{
"name": "refCount"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/router/src/apply_redirects.ts
Expand Up @@ -13,9 +13,10 @@ import {catchError, concatMap, first, last, map, mergeMap, scan, switchMap, tap}
import {RuntimeErrorCode} from './errors';
import {NavigationCancellationCode} from './events';
import {LoadedRouterConfig, Route, Routes} from './models';
import {navigationCancelingError} from './navigation_canceling_error';
import {runCanLoadGuards} from './operators/check_guards';
import {RouterConfigLoader} from './router_config_loader';
import {navigationCancelingError, Params, PRIMARY_OUTLET} from './shared';
import {Params, PRIMARY_OUTLET} from './shared';
import {createRoot, squashSegmentGroup, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {forEach} from './utils/collection';
import {getOrCreateRouteInjectorIfNeeded, getOutlet, sortByMatchingOutlets} from './utils/config';
Expand Down
4 changes: 2 additions & 2 deletions packages/router/src/index.ts
Expand Up @@ -12,10 +12,10 @@ 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, 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 {CanActivate, CanActivateChild, CanDeactivate, CanLoad, CanMatch, CanMatchFn, Data, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, 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';
export {Navigation, NavigationBehaviorOptions, NavigationExtras, Router, UrlCreationOptions} from './router';
export {Navigation, NavigationExtras, Router, UrlCreationOptions} from './router';
export {ROUTES} from './router_config_loader';
export {ExtraOptions, InitialNavigation, provideRoutes, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule} from './router_module';
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
Expand Down
53 changes: 53 additions & 0 deletions packages/router/src/models.ts
Expand Up @@ -1139,3 +1139,56 @@ export interface CanLoad {

export type CanLoadFn = (route: Route, segments: UrlSegment[]) =>
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;


/**
* @description
*
* Options that modify the `Router` navigation strategy.
* Supply an object containing any of these properties to a `Router` navigation function to
* control how the navigation should be handled.
*
* @see [Router.navigate() method](api/router/Router#navigate)
* @see [Router.navigateByUrl() method](api/router/Router#navigatebyurl)
* @see [Routing and Navigation guide](guide/router)
*
* @publicApi
*/
export interface NavigationBehaviorOptions {
/**
* When true, navigates without pushing a new state into history.
*
* ```
* // Navigate silently to /view
* this.router.navigate(['/view'], { skipLocationChange: true });
* ```
*/
skipLocationChange?: boolean;

/**
* When true, navigates while replacing the current state in history.
*
* ```
* // Navigate to /view
* this.router.navigate(['/view'], { replaceUrl: true });
* ```
*/
replaceUrl?: boolean;

/**
* Developer-defined state that can be passed to any navigation.
* Access this value through the `Navigation.extras` object
* returned from the [Router.getCurrentNavigation()
* method](api/router/Router#getcurrentnavigation) while a navigation is executing.
*
* After a navigation completes, the router writes an object containing this
* value together with a `navigationId` to `history.state`.
* The value is written when `location.go()` or `location.replaceState()`
* is called before activating this route.
*
* Note that `history.state` does not pass an object equality test because
* the router adds the `navigationId` on each navigation.
*
*/
state?: {[k: string]: any};
}
55 changes: 55 additions & 0 deletions packages/router/src/navigation_canceling_error.ts
@@ -0,0 +1,55 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NavigationCancellationCode} from './events';
import {NavigationBehaviorOptions} from './models';
import {isUrlTree, UrlSerializer, UrlTree} from './url_tree';

export const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';

export type NavigationCancelingError =
Error&{[NAVIGATION_CANCELING_ERROR]: true, cancellationCode: NavigationCancellationCode};
export type RedirectingNavigationCancelingError = NavigationCancelingError&{
url: UrlTree;
navigationBehaviorOptions?: NavigationBehaviorOptions;
cancellationCode: NavigationCancellationCode.Redirect;
};

export function redirectingNavigationError(
urlSerializer: UrlSerializer, redirect: UrlTree): RedirectingNavigationCancelingError {
const {redirectTo, navigationBehaviorOptions} =
isUrlTree(redirect) ? {redirectTo: redirect, navigationBehaviorOptions: undefined} : redirect;
const error =
navigationCancelingError(
ngDevMode && `Redirecting to "${urlSerializer.serialize(redirectTo)}"`,
NavigationCancellationCode.Redirect, redirect) as RedirectingNavigationCancelingError;
error.url = redirectTo;
error.navigationBehaviorOptions = navigationBehaviorOptions;
return error;
}

export function navigationCancelingError(
message: string|null|false, code: NavigationCancellationCode, redirectUrl?: UrlTree) {
const error = new Error(ngDevMode && 'NavigationCancelingError: ' + (message || '') || '') as
NavigationCancelingError;
error[NAVIGATION_CANCELING_ERROR] = true;
error.cancellationCode = code;
if (redirectUrl) {
(error as RedirectingNavigationCancelingError).url = redirectUrl;
}
return error;
}

export function isRedirectingNavigationCancelingError(
error: unknown|
RedirectingNavigationCancelingError): error is RedirectingNavigationCancelingError {
return isNavigationCancelingError(error) && isUrlTree((error as any).url);
}
export function isNavigationCancelingError(error: unknown): error is NavigationCancelingError {
return error && (error as any)[NAVIGATION_CANCELING_ERROR];
}
13 changes: 5 additions & 8 deletions packages/router/src/operators/check_guards.ts
Expand Up @@ -10,15 +10,15 @@ import {EnvironmentInjector, Injector} from '@angular/core';
import {concat, defer, from, MonoTypeOperatorFunction, Observable, of, OperatorFunction, pipe} from 'rxjs';
import {concatMap, first, map, mergeMap, tap} from 'rxjs/operators';

import {ActivationStart, ChildActivationStart, Event, NavigationCancellationCode} from '../events';
import {ActivationStart, ChildActivationStart, Event} from '../events';
import {CanLoad, CanLoadFn, CanMatch, CanMatchFn, Route} from '../models';
import {redirectingNavigationError} from '../navigation_canceling_error';
import {NavigationTransition} from '../router';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../router_state';
import {navigationCancelingError, REDIRECTING_CANCELLATION_REASON} from '../shared';
import {UrlSegment, UrlSerializer, UrlTree} from '../url_tree';
import {isUrlTree, UrlSegment, UrlSerializer, UrlTree} from '../url_tree';
import {wrapIntoObservable} from '../utils/collection';
import {CanActivate, CanDeactivate, getCanActivateChild, getToken} from '../utils/preactivation';
import {isBoolean, isCanActivate, isCanActivateChild, isCanDeactivate, isCanLoad, isCanMatch, isFunction, isUrlTree} from '../utils/type_guards';
import {isBoolean, isCanActivate, isCanActivateChild, isCanDeactivate, isCanLoad, isCanMatch} from '../utils/type_guards';

import {prioritizedGuardValue} from './prioritized_guard_value';

Expand Down Expand Up @@ -188,10 +188,7 @@ function redirectIfUrlTree(urlSerializer: UrlSerializer):
tap((result: UrlTree|boolean) => {
if (!isUrlTree(result)) return;

const error: Error&{url?: UrlTree} = navigationCancelingError(
REDIRECTING_CANCELLATION_REASON + urlSerializer.serialize(result),
NavigationCancellationCode.Redirect, result);
throw error;
throw redirectingNavigationError(urlSerializer, result);
}),
map(result => result === true),
);
Expand Down
3 changes: 1 addition & 2 deletions packages/router/src/operators/prioritized_guard_value.ts
Expand Up @@ -9,8 +9,7 @@
import {combineLatest, Observable, OperatorFunction} from 'rxjs';
import {filter, map, scan, startWith, switchMap, take} from 'rxjs/operators';

import {UrlTree} from '../url_tree';
import {isUrlTree} from '../utils/type_guards';
import {isUrlTree, UrlTree} from '../url_tree';

const INITIAL_VALUE = Symbol('INITIAL_VALUE');
declare type INTERIM_VALUES = typeof INITIAL_VALUE | boolean | UrlTree;
Expand Down
71 changes: 7 additions & 64 deletions packages/router/src/router.ts
Expand Up @@ -15,7 +15,8 @@ import {createRouterState} from './create_router_state';
import {createUrlTree} from './create_url_tree';
import {RuntimeErrorCode} from './errors';
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
import {QueryParamsHandling, Route, Routes} from './models';
import {NavigationBehaviorOptions, QueryParamsHandling, Route, Routes} from './models';
import {isNavigationCancelingError, isRedirectingNavigationCancelingError, redirectingNavigationError} from './navigation_canceling_error';
import {activateRoutes} from './operators/activate_routes';
import {applyRedirects} from './operators/apply_redirects';
import {checkGuards} from './operators/check_guards';
Expand All @@ -27,12 +28,11 @@ import {DefaultRouteReuseStrategy, RouteReuseStrategy} from './route_reuse_strat
import {RouterConfigLoader} from './router_config_loader';
import {ChildrenOutletContexts} from './router_outlet_context';
import {ActivatedRoute, ActivatedRouteSnapshot, createEmptyState, RouterState, RouterStateSnapshot} from './router_state';
import {isNavigationCancelingError, navigationCancelingError, Params, REDIRECTING_CANCELLATION_REASON} from './shared';
import {Params} from './shared';
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
import {containsTree, createEmptyUrlTree, IsActiveMatchOptions, UrlSerializer, UrlTree} from './url_tree';
import {containsTree, createEmptyUrlTree, IsActiveMatchOptions, isUrlTree, UrlSerializer, UrlTree} from './url_tree';
import {standardizeConfig, validateConfig} from './utils/config';
import {Checks, getAllRouteGuards} from './utils/preactivation';
import {isUrlTree} from './utils/type_guards';


const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
Expand Down Expand Up @@ -145,58 +145,6 @@ export interface UrlCreationOptions {
preserveFragment?: boolean;
}

/**
* @description
*
* Options that modify the `Router` navigation strategy.
* Supply an object containing any of these properties to a `Router` navigation function to
* control how the navigation should be handled.
*
* @see [Router.navigate() method](api/router/Router#navigate)
* @see [Router.navigateByUrl() method](api/router/Router#navigatebyurl)
* @see [Routing and Navigation guide](guide/router)
*
* @publicApi
*/
export interface NavigationBehaviorOptions {
/**
* When true, navigates without pushing a new state into history.
*
* ```
* // Navigate silently to /view
* this.router.navigate(['/view'], { skipLocationChange: true });
* ```
*/
skipLocationChange?: boolean;

/**
* When true, navigates while replacing the current state in history.
*
* ```
* // Navigate to /view
* this.router.navigate(['/view'], { replaceUrl: true });
* ```
*/
replaceUrl?: boolean;

/**
* Developer-defined state that can be passed to any navigation.
* Access this value through the `Navigation.extras` object
* returned from the [Router.getCurrentNavigation()
* method](api/router/Router#getcurrentnavigation) while a navigation is executing.
*
* After a navigation completes, the router writes an object containing this
* value together with a `navigationId` to `history.state`.
* The value is written when `location.go()` or `location.replaceState()`
* is called before activating this route.
*
* Note that `history.state` does not pass an object equality test because
* the router adds the `navigationId` on each navigation.
*
*/
state?: {[k: string]: any};
}

/**
* @description
*
Expand Down Expand Up @@ -761,11 +709,7 @@ export class Router {
checkGuards(this.ngModule.injector, (evt: Event) => this.triggerEvent(evt)),
tap(t => {
if (isUrlTree(t.guardsResult)) {
throw navigationCancelingError(
NG_DEV_MODE &&
REDIRECTING_CANCELLATION_REASON +
`"${this.serializeUrl(t.guardsResult)}"`,
NavigationCancellationCode.Redirect, t.guardsResult);
throw redirectingNavigationError(this.urlSerializer, t.guardsResult);
}

const guardsEnd = new GuardsCheckEnd(
Expand Down Expand Up @@ -929,8 +873,7 @@ export class Router {
/* This error type is issued during Redirect, and is handled as a
* cancellation rather than an error. */
if (isNavigationCancelingError(e)) {
const redirecting = isUrlTree(e.url);
if (!redirecting) {
if (!isRedirectingNavigationCancelingError(e)) {
// Set property only if we're not redirecting. If we landed on a page and
// redirect to `/` route, the new navigation is going to see the `/`
// isn't a change from the default currentUrlTree and won't navigate.
Expand All @@ -946,7 +889,7 @@ export class Router {

// When redirecting, we need to delay resolving the navigation
// promise and push it to the redirect navigation
if (!isUrlTree(e.url)) {
if (!isRedirectingNavigationCancelingError(e)) {
t.resolve(false);
} else {
const mergedTree =
Expand Down
20 changes: 1 addition & 19 deletions packages/router/src/shared.ts
Expand Up @@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {NavigationCancellationCode} from './events';
import {Route, UrlMatchResult} from './models';
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
import {UrlSegment, UrlSegmentGroup} from './url_tree';


/**
Expand Down Expand Up @@ -112,23 +111,6 @@ export function convertToParamMap(params: Params): ParamMap {
return new ParamsAsMap(params);
}

export const REDIRECTING_CANCELLATION_REASON = 'Redirecting to ';
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';

export function navigationCancelingError(
message: string|null|false, code: NavigationCancellationCode, redirectUrl?: UrlTree) {
const error = Error('NavigationCancelingError: ' + (message || ''));
(error as any)[NAVIGATION_CANCELING_ERROR] = true;
(error as any).cancellationCode = code;
(error as any).url = redirectUrl;
return error;
}

export function isNavigationCancelingError(error: Error): error is Error&
{cancellationCode: NavigationCancellationCode, url?: UrlTree} {
return error && (error as any)[NAVIGATION_CANCELING_ERROR];
}

// Matches the route configuration (`route`) against the actual URL (`segments`).
export function defaultUrlMatcher(
segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult|null {
Expand Down
4 changes: 4 additions & 0 deletions packages/router/src/url_tree.ts
Expand Up @@ -779,3 +779,7 @@ function mergeTrivialChildren(s: UrlSegmentGroup): UrlSegmentGroup {

return s;
}

export function isUrlTree(v: any): v is UrlTree {
return v instanceof UrlTree;
}

0 comments on commit 13bfdd5

Please sign in to comment.