@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
import {Injector, NgModuleRef} from '@angular/core';
|
||||
import {EmptyError, Observable, Observer, from, of } from 'rxjs';
|
||||
import {EmptyError, from, Observable, Observer, of} from 'rxjs';
|
||||
import {catchError, concatAll, every, first, map, mergeMap} from 'rxjs/operators';
|
||||
|
||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||
import {CanLoadFn} from './interfaces';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
import {PRIMARY_OUTLET, Params, defaultUrlMatcher, navigationCancelingError} from './shared';
|
||||
import {defaultUrlMatcher, navigationCancelingError, Params, PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {forEach, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
import {isCanLoad, isFunction} from './utils/type_guards';
|
||||
@ -21,7 +21,9 @@ import {isCanLoad, isFunction} from './utils/type_guards';
|
||||
class NoMatch {
|
||||
public segmentGroup: UrlSegmentGroup|null;
|
||||
|
||||
constructor(segmentGroup?: UrlSegmentGroup) { this.segmentGroup = segmentGroup || null; }
|
||||
constructor(segmentGroup?: UrlSegmentGroup) {
|
||||
this.segmentGroup = segmentGroup || null;
|
||||
}
|
||||
}
|
||||
|
||||
class AbsoluteRedirect {
|
||||
@ -46,8 +48,9 @@ function namedOutletsRedirect(redirectTo: string): Observable<any> {
|
||||
|
||||
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||
return new Observable<LoadedRouterConfig>(
|
||||
(obs: Observer<LoadedRouterConfig>) => obs.error(navigationCancelingError(
|
||||
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
|
||||
(obs: Observer<LoadedRouterConfig>) => obs.error(
|
||||
navigationCancelingError(`Cannot load children because the guard of the route "path: '${
|
||||
route.path}'" returned false`)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +79,7 @@ class ApplyRedirects {
|
||||
this.expandSegmentGroup(this.ngModule, this.config, this.urlTree.root, PRIMARY_OUTLET);
|
||||
const urlTrees$ = expanded$.pipe(
|
||||
map((rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
|
||||
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment !)));
|
||||
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment!)));
|
||||
return urlTrees$.pipe(catchError((e: any) => {
|
||||
if (e instanceof AbsoluteRedirect) {
|
||||
// after an absolute redirect we do not apply any more redirects!
|
||||
@ -98,7 +101,7 @@ class ApplyRedirects {
|
||||
this.expandSegmentGroup(this.ngModule, this.config, tree.root, PRIMARY_OUTLET);
|
||||
const mapped$ = expanded$.pipe(
|
||||
map((rootSegmentGroup: UrlSegmentGroup) =>
|
||||
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment !)));
|
||||
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment!)));
|
||||
return mapped$.pipe(catchError((e: any): Observable<UrlTree> => {
|
||||
if (e instanceof NoMatch) {
|
||||
throw this.noMatchError(e);
|
||||
@ -144,7 +147,7 @@ class ApplyRedirects {
|
||||
ngModule: NgModuleRef<any>, segmentGroup: UrlSegmentGroup, routes: Route[],
|
||||
segments: UrlSegment[], outlet: string,
|
||||
allowRedirects: boolean): Observable<UrlSegmentGroup> {
|
||||
return of (...routes).pipe(
|
||||
return of(...routes).pipe(
|
||||
map((r: any) => {
|
||||
const expanded$ = this.expandSegmentAgainstRoute(
|
||||
ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
||||
@ -152,7 +155,7 @@ class ApplyRedirects {
|
||||
if (e instanceof NoMatch) {
|
||||
// TODO(i): this return type doesn't match the declared Observable<UrlSegmentGroup> -
|
||||
// talk to Jason
|
||||
return of (null) as any;
|
||||
return of(null) as any;
|
||||
}
|
||||
throw e;
|
||||
}));
|
||||
@ -160,7 +163,7 @@ class ApplyRedirects {
|
||||
concatAll(), first((s: any) => !!s), catchError((e: any, _: any) => {
|
||||
if (e instanceof EmptyError || e.name === 'EmptyError') {
|
||||
if (this.noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
||||
return of (new UrlSegmentGroup([], {}));
|
||||
return of(new UrlSegmentGroup([], {}));
|
||||
}
|
||||
throw new NoMatch(segmentGroup);
|
||||
}
|
||||
@ -207,8 +210,8 @@ class ApplyRedirects {
|
||||
private expandWildCardWithParamsAgainstRouteUsingRedirect(
|
||||
ngModule: NgModuleRef<any>, routes: Route[], route: Route,
|
||||
outlet: string): Observable<UrlSegmentGroup> {
|
||||
const newTree = this.applyRedirectCommands([], route.redirectTo !, {});
|
||||
if (route.redirectTo !.startsWith('/')) {
|
||||
const newTree = this.applyRedirectCommands([], route.redirectTo!, {});
|
||||
if (route.redirectTo!.startsWith('/')) {
|
||||
return absoluteRedirect(newTree);
|
||||
}
|
||||
|
||||
@ -226,8 +229,8 @@ class ApplyRedirects {
|
||||
if (!matched) return noMatch(segmentGroup);
|
||||
|
||||
const newTree = this.applyRedirectCommands(
|
||||
consumedSegments, route.redirectTo !, <any>positionalParamSegments);
|
||||
if (route.redirectTo !.startsWith('/')) {
|
||||
consumedSegments, route.redirectTo!, <any>positionalParamSegments);
|
||||
if (route.redirectTo!.startsWith('/')) {
|
||||
return absoluteRedirect(newTree);
|
||||
}
|
||||
|
||||
@ -250,7 +253,7 @@ class ApplyRedirects {
|
||||
}));
|
||||
}
|
||||
|
||||
return of (new UrlSegmentGroup(segments, {}));
|
||||
return of(new UrlSegmentGroup(segments, {}));
|
||||
}
|
||||
|
||||
const {matched, consumedSegments, lastChild} = match(rawSegmentGroup, route, segments);
|
||||
@ -273,7 +276,7 @@ class ApplyRedirects {
|
||||
}
|
||||
|
||||
if (childConfig.length === 0 && slicedSegments.length === 0) {
|
||||
return of (new UrlSegmentGroup(consumedSegments, {}));
|
||||
return of(new UrlSegmentGroup(consumedSegments, {}));
|
||||
}
|
||||
|
||||
const expanded$ = this.expandSegment(
|
||||
@ -288,13 +291,13 @@ class ApplyRedirects {
|
||||
Observable<LoadedRouterConfig> {
|
||||
if (route.children) {
|
||||
// The children belong to the same module
|
||||
return of (new LoadedRouterConfig(route.children, ngModule));
|
||||
return of(new LoadedRouterConfig(route.children, ngModule));
|
||||
}
|
||||
|
||||
if (route.loadChildren) {
|
||||
// lazy children belong to the loaded module
|
||||
if (route._loadedConfig !== undefined) {
|
||||
return of (route._loadedConfig);
|
||||
return of(route._loadedConfig);
|
||||
}
|
||||
|
||||
return runCanLoadGuard(ngModule.injector, route, segments)
|
||||
@ -310,7 +313,7 @@ class ApplyRedirects {
|
||||
}));
|
||||
}
|
||||
|
||||
return of (new LoadedRouterConfig([], ngModule));
|
||||
return of(new LoadedRouterConfig([], ngModule));
|
||||
}
|
||||
|
||||
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
||||
@ -319,11 +322,11 @@ class ApplyRedirects {
|
||||
while (true) {
|
||||
res = res.concat(c.segments);
|
||||
if (c.numberOfChildren === 0) {
|
||||
return of (res);
|
||||
return of(res);
|
||||
}
|
||||
|
||||
if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
|
||||
return namedOutletsRedirect(route.redirectTo !);
|
||||
return namedOutletsRedirect(route.redirectTo!);
|
||||
}
|
||||
|
||||
c = c.children[PRIMARY_OUTLET];
|
||||
@ -406,7 +409,7 @@ class ApplyRedirects {
|
||||
function runCanLoadGuard(
|
||||
moduleInjector: Injector, route: Route, segments: UrlSegment[]): Observable<boolean> {
|
||||
const canLoad = route.canLoad;
|
||||
if (!canLoad || canLoad.length === 0) return of (true);
|
||||
if (!canLoad || canLoad.length === 0) return of(true);
|
||||
|
||||
const obs = from(canLoad).pipe(map((injectionToken: any) => {
|
||||
const guard = moduleInjector.get(injectionToken);
|
||||
@ -452,9 +455,9 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
|
||||
|
||||
return {
|
||||
matched: true,
|
||||
consumedSegments: res.consumed !,
|
||||
lastChild: res.consumed.length !,
|
||||
positionalParamSegments: res.posParams !,
|
||||
consumedSegments: res.consumed!,
|
||||
lastChild: res.consumed.length!,
|
||||
positionalParamSegments: res.posParams!,
|
||||
};
|
||||
}
|
||||
|
||||
@ -464,16 +467,18 @@ function split(
|
||||
if (slicedSegments.length > 0 &&
|
||||
containsEmptyPathRedirectsWithNamedOutlets(segmentGroup, slicedSegments, config)) {
|
||||
const s = new UrlSegmentGroup(
|
||||
consumedSegments, createChildrenForEmptySegments(
|
||||
config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
||||
consumedSegments,
|
||||
createChildrenForEmptySegments(
|
||||
config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
||||
return {segmentGroup: mergeTrivialChildren(s), slicedSegments: []};
|
||||
}
|
||||
|
||||
if (slicedSegments.length === 0 &&
|
||||
containsEmptyPathRedirects(segmentGroup, slicedSegments, config)) {
|
||||
const s = new UrlSegmentGroup(
|
||||
segmentGroup.segments, addEmptySegmentsToChildrenIfNeeded(
|
||||
segmentGroup, slicedSegments, config, segmentGroup.children));
|
||||
segmentGroup.segments,
|
||||
addEmptySegmentsToChildrenIfNeeded(
|
||||
segmentGroup, slicedSegments, config, segmentGroup.children));
|
||||
return {segmentGroup: mergeTrivialChildren(s), slicedSegments};
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ActivatedRoute} from './router_state';
|
||||
import {PRIMARY_OUTLET, Params} from './shared';
|
||||
import {Params, PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {forEach, last, shallowEqual} from './utils/collection';
|
||||
|
||||
@ -164,7 +164,7 @@ function createPositionApplyingDoubleDots(
|
||||
let dd = numberOfDoubleDots;
|
||||
while (dd > ci) {
|
||||
dd -= ci;
|
||||
g = g.parent !;
|
||||
g = g.parent!;
|
||||
if (!g) {
|
||||
throw new Error('Invalid number of \'../\'');
|
||||
}
|
||||
|
@ -76,14 +76,12 @@ import {RouterLink, RouterLinkWithHref} from './router_link';
|
||||
selector: '[routerLinkActive]',
|
||||
exportAs: 'routerLinkActive',
|
||||
})
|
||||
export class RouterLinkActive implements OnChanges,
|
||||
OnDestroy, AfterContentInit {
|
||||
export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@ContentChildren(RouterLink, {descendants: true})
|
||||
links !: QueryList<RouterLink>;
|
||||
@ContentChildren(RouterLink, {descendants: true}) links!: QueryList<RouterLink>;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@ContentChildren(RouterLinkWithHref, {descendants: true})
|
||||
linksWithHrefs !: QueryList<RouterLinkWithHref>;
|
||||
linksWithHrefs!: QueryList<RouterLinkWithHref>;
|
||||
|
||||
private classes: string[] = [];
|
||||
private subscription: Subscription;
|
||||
@ -115,8 +113,12 @@ export class RouterLinkActive implements OnChanges,
|
||||
this.classes = classes.filter(c => !!c);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void { this.update(); }
|
||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.update();
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
|
||||
@ -136,7 +138,7 @@ export class RouterLinkActive implements OnChanges,
|
||||
}
|
||||
|
||||
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
|
||||
return (link: RouterLink | RouterLinkWithHref) =>
|
||||
return (link: RouterLink|RouterLinkWithHref) =>
|
||||
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,9 @@ export class RouterOutlet implements OnDestroy, OnInit {
|
||||
parentContexts.onChildOutletCreated(this.name, this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(this.name); }
|
||||
ngOnDestroy(): void {
|
||||
this.parentContexts.onChildOutletDestroyed(this.name);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.activated) {
|
||||
@ -75,7 +77,9 @@ export class RouterOutlet implements OnDestroy, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
get isActivated(): boolean { return !!this.activated; }
|
||||
get isActivated(): boolean {
|
||||
return !!this.activated;
|
||||
}
|
||||
|
||||
get component(): Object {
|
||||
if (!this.activated) throw new Error('Outlet is not activated');
|
||||
@ -131,7 +135,7 @@ export class RouterOutlet implements OnDestroy, OnInit {
|
||||
}
|
||||
this._activatedRoute = activatedRoute;
|
||||
const snapshot = activatedRoute._futureSnapshot;
|
||||
const component = <any>snapshot.routeConfig !.component;
|
||||
const component = <any>snapshot.routeConfig!.component;
|
||||
resolver = resolver || this.resolver;
|
||||
const factory = resolver.resolveComponentFactory(component);
|
||||
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
||||
|
@ -18,7 +18,7 @@ import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type NavigationTrigger = 'imperative' | 'popstate' | 'hashchange';
|
||||
export type NavigationTrigger = 'imperative'|'popstate'|'hashchange';
|
||||
|
||||
/**
|
||||
* Base for events the router goes through, as opposed to events tied to a specific
|
||||
@ -96,7 +96,9 @@ export class NavigationStart extends RouterEvent {
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string { return `NavigationStart(id: ${this.id}, url: '${this.url}')`; }
|
||||
toString(): string {
|
||||
return `NavigationStart(id: ${this.id}, url: '${this.url}')`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +119,8 @@ export class NavigationEnd extends RouterEvent {
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string {
|
||||
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
|
||||
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}')`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +144,9 @@ export class NavigationCancel extends RouterEvent {
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
|
||||
toString(): string {
|
||||
return `NavigationCancel(id: ${this.id}, url: '${this.url}')`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,7 +191,8 @@ export class RoutesRecognized extends RouterEvent {
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string {
|
||||
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +215,8 @@ export class GuardsCheckStart extends RouterEvent {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +241,8 @@ export class GuardsCheckEnd extends RouterEvent {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
||||
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +268,8 @@ export class ResolveStart extends RouterEvent {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +293,8 @@ export class ResolveEnd extends RouterEvent {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,7 +307,9 @@ export class RouteConfigLoadStart {
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public route: Route) {}
|
||||
toString(): string { return `RouteConfigLoadStart(path: ${this.route.path})`; }
|
||||
toString(): string {
|
||||
return `RouteConfigLoadStart(path: ${this.route.path})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,7 +321,9 @@ export class RouteConfigLoadEnd {
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public route: Route) {}
|
||||
toString(): string { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
|
||||
toString(): string {
|
||||
return `RouteConfigLoadEnd(path: ${this.route.path})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,5 +443,5 @@ export class Scroll {
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type Event = RouterEvent | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart |
|
||||
ChildActivationEnd | ActivationStart | ActivationEnd | Scroll;
|
||||
export type Event = RouterEvent|RouteConfigLoadStart|RouteConfigLoadEnd|ChildActivationStart|
|
||||
ChildActivationEnd|ActivationStart|ActivationEnd|Scroll;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
|
||||
export {Data, DeprecatedLoadChildren, LoadChildren, LoadChildrenCallback, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatchResult, UrlMatcher} from './config';
|
||||
export {Data, DeprecatedLoadChildren, LoadChildren, LoadChildrenCallback, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './config';
|
||||
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
export {RouterLinkActive} from './directives/router_link_active';
|
||||
export {RouterOutlet} from './directives/router_outlet';
|
||||
@ -16,11 +16,11 @@ export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './
|
||||
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
export {Navigation, NavigationExtras, Router} from './router';
|
||||
export {ROUTES} from './router_config_loader';
|
||||
export {ExtraOptions, InitialNavigation, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
|
||||
export {ExtraOptions, InitialNavigation, provideRoutes, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule} from './router_module';
|
||||
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
||||
export {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
|
||||
export {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';
|
||||
export {convertToParamMap, ParamMap, Params, PRIMARY_OUTLET} from './shared';
|
||||
export {UrlHandlingStrategy} from './url_handling_strategy';
|
||||
export {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
export {VERSION} from './version';
|
||||
|
@ -88,7 +88,7 @@ export interface CanActivate {
|
||||
}
|
||||
|
||||
export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) =>
|
||||
Observable<boolean|UrlTree>| Promise<boolean|UrlTree>| boolean | UrlTree;
|
||||
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
|
||||
|
||||
/**
|
||||
* @description
|
||||
@ -175,7 +175,7 @@ export interface CanActivateChild {
|
||||
}
|
||||
|
||||
export type CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) =>
|
||||
Observable<boolean|UrlTree>| Promise<boolean|UrlTree>| boolean | UrlTree;
|
||||
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
|
||||
|
||||
/**
|
||||
* @description
|
||||
@ -259,7 +259,7 @@ export interface CanDeactivate<T> {
|
||||
export type CanDeactivateFn<T> =
|
||||
(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot,
|
||||
nextState?: RouterStateSnapshot) =>
|
||||
Observable<boolean|UrlTree>| Promise<boolean|UrlTree>| boolean | UrlTree;
|
||||
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
|
||||
|
||||
/**
|
||||
* @description
|
||||
@ -404,4 +404,4 @@ export interface CanLoad {
|
||||
}
|
||||
|
||||
export type CanLoadFn = (route: Route, segments: UrlSegment[]) =>
|
||||
Observable<boolean>| Promise<boolean>| boolean;
|
||||
Observable<boolean>|Promise<boolean>|boolean;
|
||||
|
@ -14,16 +14,16 @@ import {ActivationEnd, ChildActivationEnd, Event} from '../events';
|
||||
import {DetachedRouteHandleInternal, RouteReuseStrategy} from '../route_reuse_strategy';
|
||||
import {NavigationTransition} from '../router';
|
||||
import {ChildrenOutletContexts} from '../router_outlet_context';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, advanceActivatedRoute} from '../router_state';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, advanceActivatedRoute, RouterState} from '../router_state';
|
||||
import {forEach} from '../utils/collection';
|
||||
import {TreeNode, nodeChildrenAsMap} from '../utils/tree';
|
||||
import {nodeChildrenAsMap, TreeNode} from '../utils/tree';
|
||||
|
||||
export const activateRoutes =
|
||||
(rootContexts: ChildrenOutletContexts, routeReuseStrategy: RouteReuseStrategy,
|
||||
forwardEvent: (evt: Event) => void): MonoTypeOperatorFunction<NavigationTransition> =>
|
||||
map(t => {
|
||||
new ActivateRoutes(
|
||||
routeReuseStrategy, t.targetRouterState !, t.currentRouterState, forwardEvent)
|
||||
routeReuseStrategy, t.targetRouterState!, t.currentRouterState, forwardEvent)
|
||||
.activate(rootContexts);
|
||||
return t;
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '@angular/core';
|
||||
import {MonoTypeOperatorFunction, Observable, defer, from, of } from 'rxjs';
|
||||
import {defer, from, MonoTypeOperatorFunction, Observable, of} from 'rxjs';
|
||||
import {concatAll, concatMap, first, map, mergeMap} from 'rxjs/operators';
|
||||
|
||||
import {ActivationStart, ChildActivationStart, Event} from '../events';
|
||||
@ -24,21 +24,20 @@ import {prioritizedGuardValue} from './prioritized_guard_value';
|
||||
export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event) => void):
|
||||
MonoTypeOperatorFunction<NavigationTransition> {
|
||||
return function(source: Observable<NavigationTransition>) {
|
||||
|
||||
return source.pipe(mergeMap(t => {
|
||||
const {targetSnapshot, currentSnapshot, guards: {canActivateChecks, canDeactivateChecks}} = t;
|
||||
if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
|
||||
return of ({...t, guardsResult: true});
|
||||
return of({...t, guardsResult: true});
|
||||
}
|
||||
|
||||
return runCanDeactivateChecks(
|
||||
canDeactivateChecks, targetSnapshot !, currentSnapshot, moduleInjector)
|
||||
canDeactivateChecks, targetSnapshot!, currentSnapshot, moduleInjector)
|
||||
.pipe(
|
||||
mergeMap(canDeactivate => {
|
||||
return canDeactivate && isBoolean(canDeactivate) ?
|
||||
runCanActivateChecks(
|
||||
targetSnapshot !, canActivateChecks, moduleInjector, forwardEvent) :
|
||||
of (canDeactivate);
|
||||
targetSnapshot!, canActivateChecks, moduleInjector, forwardEvent) :
|
||||
of(canDeactivate);
|
||||
}),
|
||||
map(guardsResult => ({...t, guardsResult})));
|
||||
}));
|
||||
@ -52,7 +51,9 @@ function runCanDeactivateChecks(
|
||||
mergeMap(
|
||||
check =>
|
||||
runCanDeactivate(check.component, check.route, currRSS, futureRSS, moduleInjector)),
|
||||
first(result => { return result !== true; }, true as boolean | UrlTree));
|
||||
first(result => {
|
||||
return result !== true;
|
||||
}, true as boolean | UrlTree));
|
||||
}
|
||||
|
||||
function runCanActivateChecks(
|
||||
@ -70,48 +71,50 @@ function runCanActivateChecks(
|
||||
return result !== true;
|
||||
}, true as boolean | UrlTree));
|
||||
}),
|
||||
first(result => { return result !== true; }, true as boolean | UrlTree));
|
||||
first(result => {
|
||||
return result !== true;
|
||||
}, true as boolean | UrlTree));
|
||||
}
|
||||
|
||||
/**
|
||||
* This should fire off `ActivationStart` events for each route being activated at this
|
||||
* level.
|
||||
* In other words, if you're activating `a` and `b` below, `path` will contain the
|
||||
* `ActivatedRouteSnapshot`s for both and we will fire `ActivationStart` for both. Always
|
||||
* return
|
||||
* `true` so checks continue to run.
|
||||
*/
|
||||
* This should fire off `ActivationStart` events for each route being activated at this
|
||||
* level.
|
||||
* In other words, if you're activating `a` and `b` below, `path` will contain the
|
||||
* `ActivatedRouteSnapshot`s for both and we will fire `ActivationStart` for both. Always
|
||||
* return
|
||||
* `true` so checks continue to run.
|
||||
*/
|
||||
function fireActivationStart(
|
||||
snapshot: ActivatedRouteSnapshot | null,
|
||||
snapshot: ActivatedRouteSnapshot|null,
|
||||
forwardEvent?: (evt: Event) => void): Observable<boolean> {
|
||||
if (snapshot !== null && forwardEvent) {
|
||||
forwardEvent(new ActivationStart(snapshot));
|
||||
}
|
||||
return of (true);
|
||||
return of(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should fire off `ChildActivationStart` events for each route being activated at this
|
||||
* level.
|
||||
* In other words, if you're activating `a` and `b` below, `path` will contain the
|
||||
* `ActivatedRouteSnapshot`s for both and we will fire `ChildActivationStart` for both. Always
|
||||
* return
|
||||
* `true` so checks continue to run.
|
||||
*/
|
||||
* This should fire off `ChildActivationStart` events for each route being activated at this
|
||||
* level.
|
||||
* In other words, if you're activating `a` and `b` below, `path` will contain the
|
||||
* `ActivatedRouteSnapshot`s for both and we will fire `ChildActivationStart` for both. Always
|
||||
* return
|
||||
* `true` so checks continue to run.
|
||||
*/
|
||||
function fireChildActivationStart(
|
||||
snapshot: ActivatedRouteSnapshot | null,
|
||||
snapshot: ActivatedRouteSnapshot|null,
|
||||
forwardEvent?: (evt: Event) => void): Observable<boolean> {
|
||||
if (snapshot !== null && forwardEvent) {
|
||||
forwardEvent(new ChildActivationStart(snapshot));
|
||||
}
|
||||
return of (true);
|
||||
return of(true);
|
||||
}
|
||||
|
||||
function runCanActivate(
|
||||
futureRSS: RouterStateSnapshot, futureARS: ActivatedRouteSnapshot,
|
||||
moduleInjector: Injector): Observable<boolean|UrlTree> {
|
||||
const canActivate = futureARS.routeConfig ? futureARS.routeConfig.canActivate : null;
|
||||
if (!canActivate || canActivate.length === 0) return of (true);
|
||||
if (!canActivate || canActivate.length === 0) return of(true);
|
||||
|
||||
const canActivateObservables = canActivate.map((c: any) => {
|
||||
return defer(() => {
|
||||
@ -127,7 +130,7 @@ function runCanActivate(
|
||||
return observable.pipe(first());
|
||||
});
|
||||
});
|
||||
return of (canActivateObservables).pipe(prioritizedGuardValue());
|
||||
return of(canActivateObservables).pipe(prioritizedGuardValue());
|
||||
}
|
||||
|
||||
function runCanActivateChild(
|
||||
@ -154,23 +157,22 @@ function runCanActivateChild(
|
||||
}
|
||||
return observable.pipe(first());
|
||||
});
|
||||
return of (guardsMapped).pipe(prioritizedGuardValue());
|
||||
return of(guardsMapped).pipe(prioritizedGuardValue());
|
||||
});
|
||||
});
|
||||
return of (canActivateChildGuardsMapped).pipe(prioritizedGuardValue());
|
||||
return of(canActivateChildGuardsMapped).pipe(prioritizedGuardValue());
|
||||
}
|
||||
|
||||
function runCanDeactivate(
|
||||
component: Object | null, currARS: ActivatedRouteSnapshot, currRSS: RouterStateSnapshot,
|
||||
component: Object|null, currARS: ActivatedRouteSnapshot, currRSS: RouterStateSnapshot,
|
||||
futureRSS: RouterStateSnapshot, moduleInjector: Injector): Observable<boolean|UrlTree> {
|
||||
const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null;
|
||||
if (!canDeactivate || canDeactivate.length === 0) return of (true);
|
||||
if (!canDeactivate || canDeactivate.length === 0) return of(true);
|
||||
const canDeactivateObservables = canDeactivate.map((c: any) => {
|
||||
const guard = getToken(c, currARS, moduleInjector);
|
||||
let observable;
|
||||
if (isCanDeactivate(guard)) {
|
||||
observable =
|
||||
wrapIntoObservable(guard.canDeactivate(component !, currARS, currRSS, futureRSS));
|
||||
observable = wrapIntoObservable(guard.canDeactivate(component!, currARS, currRSS, futureRSS));
|
||||
} else if (isFunction<CanDeactivateFn<any>>(guard)) {
|
||||
observable = wrapIntoObservable(guard(component, currARS, currRSS, futureRSS));
|
||||
} else {
|
||||
@ -178,5 +180,5 @@ function runCanDeactivate(
|
||||
}
|
||||
return observable.pipe(first());
|
||||
});
|
||||
return of (canDeactivateObservables).pipe(prioritizedGuardValue());
|
||||
return of(canDeactivateObservables).pipe(prioritizedGuardValue());
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Observable, OperatorFunction, combineLatest} from 'rxjs';
|
||||
import {combineLatest, Observable, OperatorFunction} from 'rxjs';
|
||||
import {filter, map, scan, startWith, switchMap, take} from 'rxjs/operators';
|
||||
|
||||
import {UrlTree} from '../url_tree';
|
||||
@ -20,36 +20,36 @@ export function prioritizedGuardValue():
|
||||
return switchMap(obs => {
|
||||
return combineLatest(
|
||||
...obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE as INTERIM_VALUES))))
|
||||
.pipe(
|
||||
scan(
|
||||
(acc: INTERIM_VALUES, list: INTERIM_VALUES[]) => {
|
||||
let isPending = false;
|
||||
return list.reduce((innerAcc, val, i: number) => {
|
||||
if (innerAcc !== INITIAL_VALUE) return innerAcc;
|
||||
.pipe(
|
||||
scan(
|
||||
(acc: INTERIM_VALUES, list: INTERIM_VALUES[]) => {
|
||||
let isPending = false;
|
||||
return list.reduce((innerAcc, val, i: number) => {
|
||||
if (innerAcc !== INITIAL_VALUE) return innerAcc;
|
||||
|
||||
// Toggle pending flag if any values haven't been set yet
|
||||
if (val === INITIAL_VALUE) isPending = true;
|
||||
// Toggle pending flag if any values haven't been set yet
|
||||
if (val === INITIAL_VALUE) isPending = true;
|
||||
|
||||
// Any other return values are only valid if we haven't yet hit a pending call.
|
||||
// This guarantees that in the case of a guard at the bottom of the tree that
|
||||
// returns a redirect, we will wait for the higher priority guard at the top to
|
||||
// finish before performing the redirect.
|
||||
if (!isPending) {
|
||||
// Early return when we hit a `false` value as that should always cancel
|
||||
// navigation
|
||||
if (val === false) return val;
|
||||
// Any other return values are only valid if we haven't yet hit a pending
|
||||
// call. This guarantees that in the case of a guard at the bottom of the
|
||||
// tree that returns a redirect, we will wait for the higher priority
|
||||
// guard at the top to finish before performing the redirect.
|
||||
if (!isPending) {
|
||||
// Early return when we hit a `false` value as that should always
|
||||
// cancel navigation
|
||||
if (val === false) return val;
|
||||
|
||||
if (i === list.length - 1 || isUrlTree(val)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
if (i === list.length - 1 || isUrlTree(val)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return innerAcc;
|
||||
}, acc);
|
||||
},
|
||||
INITIAL_VALUE),
|
||||
filter(item => item !== INITIAL_VALUE),
|
||||
map(item => isUrlTree(item) ? item : item === true), //
|
||||
take(1)) as Observable<boolean|UrlTree>;
|
||||
return innerAcc;
|
||||
}, acc);
|
||||
},
|
||||
INITIAL_VALUE),
|
||||
filter(item => item !== INITIAL_VALUE),
|
||||
map(item => isUrlTree(item) ? item : item === true), //
|
||||
take(1)) as Observable<boolean|UrlTree>;
|
||||
});
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ import {NavigationTransition} from '../router';
|
||||
import {UrlTree} from '../url_tree';
|
||||
|
||||
export function recognize(
|
||||
rootComponentType: Type<any>| null, config: Route[], serializer: (url: UrlTree) => string,
|
||||
paramsInheritanceStrategy: 'emptyOnly' | 'always', relativeLinkResolution: 'legacy' |
|
||||
'corrected'): MonoTypeOperatorFunction<NavigationTransition> {
|
||||
rootComponentType: Type<any>|null, config: Route[], serializer: (url: UrlTree) => string,
|
||||
paramsInheritanceStrategy: 'emptyOnly'|'always',
|
||||
relativeLinkResolution: 'legacy'|'corrected'): MonoTypeOperatorFunction<NavigationTransition> {
|
||||
return function(source: Observable<NavigationTransition>) {
|
||||
return source.pipe(mergeMap(
|
||||
t => recognizeFn(
|
||||
|
@ -7,32 +7,31 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '@angular/core';
|
||||
import {MonoTypeOperatorFunction, Observable, from, of } from 'rxjs';
|
||||
import {from, MonoTypeOperatorFunction, Observable, of} from 'rxjs';
|
||||
import {concatMap, last, map, mergeMap, reduce} from 'rxjs/operators';
|
||||
|
||||
import {ResolveData} from '../config';
|
||||
import {NavigationTransition} from '../router';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from '../router_state';
|
||||
import {ActivatedRouteSnapshot, inheritedParamsDataResolve, RouterStateSnapshot} from '../router_state';
|
||||
import {wrapIntoObservable} from '../utils/collection';
|
||||
|
||||
import {getToken} from '../utils/preactivation';
|
||||
|
||||
export function resolveData(
|
||||
paramsInheritanceStrategy: 'emptyOnly' | 'always',
|
||||
paramsInheritanceStrategy: 'emptyOnly'|'always',
|
||||
moduleInjector: Injector): MonoTypeOperatorFunction<NavigationTransition> {
|
||||
return function(source: Observable<NavigationTransition>) {
|
||||
return source.pipe(mergeMap(t => {
|
||||
const {targetSnapshot, guards: {canActivateChecks}} = t;
|
||||
|
||||
if (!canActivateChecks.length) {
|
||||
return of (t);
|
||||
return of(t);
|
||||
}
|
||||
|
||||
return from(canActivateChecks)
|
||||
.pipe(
|
||||
concatMap(
|
||||
check => runResolve(
|
||||
check.route, targetSnapshot !, paramsInheritanceStrategy, moduleInjector)),
|
||||
check.route, targetSnapshot!, paramsInheritanceStrategy, moduleInjector)),
|
||||
reduce((_: any, __: any) => _), map(_ => t));
|
||||
}));
|
||||
};
|
||||
@ -40,14 +39,15 @@ export function resolveData(
|
||||
|
||||
function runResolve(
|
||||
futureARS: ActivatedRouteSnapshot, futureRSS: RouterStateSnapshot,
|
||||
paramsInheritanceStrategy: 'emptyOnly' | 'always', moduleInjector: Injector) {
|
||||
paramsInheritanceStrategy: 'emptyOnly'|'always', moduleInjector: Injector) {
|
||||
const resolve = futureARS._resolve;
|
||||
return resolveNode(resolve, futureARS, futureRSS, moduleInjector)
|
||||
.pipe(map((resolvedData: any) => {
|
||||
futureARS._resolvedData = resolvedData;
|
||||
futureARS.data = {
|
||||
...futureARS.data,
|
||||
...inheritedParamsDataResolve(futureARS, paramsInheritanceStrategy).resolve};
|
||||
...futureARS.data,
|
||||
...inheritedParamsDataResolve(futureARS, paramsInheritanceStrategy).resolve
|
||||
};
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
@ -57,12 +57,14 @@ function resolveNode(
|
||||
moduleInjector: Injector): Observable<any> {
|
||||
const keys = Object.keys(resolve);
|
||||
if (keys.length === 0) {
|
||||
return of ({});
|
||||
return of({});
|
||||
}
|
||||
if (keys.length === 1) {
|
||||
const key = keys[0];
|
||||
return getResolver(resolve[key], futureARS, futureRSS, moduleInjector)
|
||||
.pipe(map((value: any) => { return {[key]: value}; }));
|
||||
.pipe(map((value: any) => {
|
||||
return {[key]: value};
|
||||
}));
|
||||
}
|
||||
const data: {[k: string]: any} = {};
|
||||
const runningResolvers$ = from(keys).pipe(mergeMap((key: string) => {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MonoTypeOperatorFunction, ObservableInput, from} from 'rxjs';
|
||||
import {from, MonoTypeOperatorFunction, ObservableInput} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
|
@ -7,21 +7,21 @@
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {Observable, Observer, of } from 'rxjs';
|
||||
import {Observable, Observer, of} from 'rxjs';
|
||||
|
||||
import {Data, ResolveData, Route, Routes} from './config';
|
||||
import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
|
||||
import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree';
|
||||
import {ActivatedRouteSnapshot, inheritedParamsDataResolve, ParamsInheritanceStrategy, RouterStateSnapshot} from './router_state';
|
||||
import {defaultUrlMatcher, PRIMARY_OUTLET} from './shared';
|
||||
import {mapChildrenIntoArray, UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {forEach, last} from './utils/collection';
|
||||
import {TreeNode} from './utils/tree';
|
||||
|
||||
class NoMatch {}
|
||||
|
||||
export function recognize(
|
||||
rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, url: string,
|
||||
rootComponentType: Type<any>|null, config: Routes, urlTree: UrlTree, url: string,
|
||||
paramsInheritanceStrategy: ParamsInheritanceStrategy = 'emptyOnly',
|
||||
relativeLinkResolution: 'legacy' | 'corrected' = 'legacy'): Observable<RouterStateSnapshot> {
|
||||
relativeLinkResolution: 'legacy'|'corrected' = 'legacy'): Observable<RouterStateSnapshot> {
|
||||
return new Recognizer(
|
||||
rootComponentType, config, urlTree, url, paramsInheritanceStrategy,
|
||||
relativeLinkResolution)
|
||||
@ -43,13 +43,13 @@ class Recognizer {
|
||||
|
||||
const root = new ActivatedRouteSnapshot(
|
||||
[], Object.freeze({}), Object.freeze({...this.urlTree.queryParams}),
|
||||
this.urlTree.fragment !, {}, PRIMARY_OUTLET, this.rootComponentType, null,
|
||||
this.urlTree.fragment!, {}, PRIMARY_OUTLET, this.rootComponentType, null,
|
||||
this.urlTree.root, -1, {});
|
||||
|
||||
const rootNode = new TreeNode<ActivatedRouteSnapshot>(root, children);
|
||||
const routeState = new RouterStateSnapshot(this.url, rootNode);
|
||||
this.inheritParamsAndData(routeState._root);
|
||||
return of (routeState);
|
||||
return of(routeState);
|
||||
|
||||
} catch (e) {
|
||||
return new Observable<RouterStateSnapshot>(
|
||||
@ -119,10 +119,10 @@ class Recognizer {
|
||||
let rawSlicedSegments: UrlSegment[] = [];
|
||||
|
||||
if (route.path === '**') {
|
||||
const params = segments.length > 0 ? last(segments) !.parameters : {};
|
||||
const params = segments.length > 0 ? last(segments)!.parameters : {};
|
||||
snapshot = new ActivatedRouteSnapshot(
|
||||
segments, params, Object.freeze({...this.urlTree.queryParams}), this.urlTree.fragment !,
|
||||
getData(route), outlet, route.component !, route, getSourceSegmentGroup(rawSegment),
|
||||
segments, params, Object.freeze({...this.urlTree.queryParams}), this.urlTree.fragment!,
|
||||
getData(route), outlet, route.component!, route, getSourceSegmentGroup(rawSegment),
|
||||
getPathIndexShift(rawSegment) + segments.length, getResolve(route));
|
||||
} else {
|
||||
const result: MatchResult = match(rawSegment, route, segments);
|
||||
@ -131,7 +131,7 @@ class Recognizer {
|
||||
|
||||
snapshot = new ActivatedRouteSnapshot(
|
||||
consumedSegments, result.parameters, Object.freeze({...this.urlTree.queryParams}),
|
||||
this.urlTree.fragment !, getData(route), outlet, route.component !, route,
|
||||
this.urlTree.fragment!, getData(route), outlet, route.component!, route,
|
||||
getSourceSegmentGroup(rawSegment),
|
||||
getPathIndexShift(rawSegment) + consumedSegments.length, getResolve(route));
|
||||
}
|
||||
@ -169,7 +169,7 @@ function getChildConfig(route: Route): Route[] {
|
||||
}
|
||||
|
||||
if (route.loadChildren) {
|
||||
return route._loadedConfig !.routes;
|
||||
return route._loadedConfig!.routes;
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -195,7 +195,9 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
|
||||
if (!res) throw new NoMatch();
|
||||
|
||||
const posParams: {[n: string]: string} = {};
|
||||
forEach(res.posParams !, (v: UrlSegment, k: string) => { posParams[k] = v.path; });
|
||||
forEach(res.posParams!, (v: UrlSegment, k: string) => {
|
||||
posParams[k] = v.path;
|
||||
});
|
||||
const parameters = res.consumed.length > 0 ?
|
||||
{...posParams, ...res.consumed[res.consumed.length - 1].parameters} :
|
||||
posParams;
|
||||
@ -236,13 +238,14 @@ function getPathIndexShift(segmentGroup: UrlSegmentGroup): number {
|
||||
|
||||
function split(
|
||||
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
|
||||
config: Route[], relativeLinkResolution: 'legacy' | 'corrected') {
|
||||
config: Route[], relativeLinkResolution: 'legacy'|'corrected') {
|
||||
if (slicedSegments.length > 0 &&
|
||||
containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) {
|
||||
const s = new UrlSegmentGroup(
|
||||
consumedSegments, createChildrenForEmptyPaths(
|
||||
segmentGroup, consumedSegments, config,
|
||||
new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
||||
consumedSegments,
|
||||
createChildrenForEmptyPaths(
|
||||
segmentGroup, consumedSegments, config,
|
||||
new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
||||
s._sourceSegment = segmentGroup;
|
||||
s._segmentIndexShift = consumedSegments.length;
|
||||
return {segmentGroup: s, slicedSegments: []};
|
||||
@ -251,9 +254,10 @@ function split(
|
||||
if (slicedSegments.length === 0 &&
|
||||
containsEmptyPathMatches(segmentGroup, slicedSegments, config)) {
|
||||
const s = new UrlSegmentGroup(
|
||||
segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(
|
||||
segmentGroup, consumedSegments, slicedSegments, config,
|
||||
segmentGroup.children, relativeLinkResolution));
|
||||
segmentGroup.segments,
|
||||
addEmptyPathsToChildrenIfNeeded(
|
||||
segmentGroup, consumedSegments, slicedSegments, config, segmentGroup.children,
|
||||
relativeLinkResolution));
|
||||
s._sourceSegment = segmentGroup;
|
||||
s._segmentIndexShift = consumedSegments.length;
|
||||
return {segmentGroup: s, slicedSegments};
|
||||
@ -268,7 +272,7 @@ function split(
|
||||
function addEmptyPathsToChildrenIfNeeded(
|
||||
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
|
||||
routes: Route[], children: {[name: string]: UrlSegmentGroup},
|
||||
relativeLinkResolution: 'legacy' | 'corrected'): {[name: string]: UrlSegmentGroup} {
|
||||
relativeLinkResolution: 'legacy'|'corrected'): {[name: string]: UrlSegmentGroup} {
|
||||
const res: {[name: string]: UrlSegmentGroup} = {};
|
||||
for (const r of routes) {
|
||||
if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
|
||||
|
@ -63,10 +63,16 @@ export abstract class RouteReuseStrategy {
|
||||
* Does not detach any subtrees. Reuses routes as long as their route config is the same.
|
||||
*/
|
||||
export class DefaultRouteReuseStrategy implements RouteReuseStrategy {
|
||||
shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
|
||||
shouldDetach(route: ActivatedRouteSnapshot): boolean {
|
||||
return false;
|
||||
}
|
||||
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
|
||||
shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
|
||||
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; }
|
||||
shouldAttach(route: ActivatedRouteSnapshot): boolean {
|
||||
return false;
|
||||
}
|
||||
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null {
|
||||
return null;
|
||||
}
|
||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||
return future.routeConfig === curr.routeConfig;
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import {Location} from '@angular/common';
|
||||
import {Compiler, Injector, NgModuleFactoryLoader, NgModuleRef, NgZone, Type, isDevMode, ɵConsole as Console} from '@angular/core';
|
||||
import {BehaviorSubject, EMPTY, Observable, Subject, Subscription, defer, of } from 'rxjs';
|
||||
import {Compiler, Injector, isDevMode, NgModuleFactoryLoader, NgModuleRef, NgZone, Type, ɵConsole as Console} from '@angular/core';
|
||||
import {BehaviorSubject, defer, EMPTY, Observable, of, Subject, Subscription} from 'rxjs';
|
||||
import {catchError, filter, finalize, map, switchMap, tap} from 'rxjs/operators';
|
||||
|
||||
import {QueryParamsHandling, Route, Routes, standardizeConfig, validateConfig} from './config';
|
||||
@ -24,10 +24,10 @@ import {switchTap} from './operators/switch_tap';
|
||||
import {DefaultRouteReuseStrategy, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
import {ChildrenOutletContexts} from './router_outlet_context';
|
||||
import {ActivatedRoute, RouterState, RouterStateSnapshot, createEmptyState} from './router_state';
|
||||
import {Params, isNavigationCancelingError, navigationCancelingError} from './shared';
|
||||
import {ActivatedRoute, createEmptyState, RouterState, RouterStateSnapshot} from './router_state';
|
||||
import {isNavigationCancelingError, navigationCancelingError, Params} from './shared';
|
||||
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
||||
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
||||
import {containsTree, createEmptyUrlTree, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {Checks, getAllRouteGuards} from './utils/preactivation';
|
||||
import {isUrlTree} from './utils/type_guards';
|
||||
|
||||
@ -49,16 +49,16 @@ export interface NavigationExtras {
|
||||
*
|
||||
* ```
|
||||
* [{
|
||||
* path: 'parent',
|
||||
* component: ParentComponent,
|
||||
* children: [{
|
||||
* path: 'list',
|
||||
* component: ListComponent
|
||||
* },{
|
||||
* path: 'child',
|
||||
* component: ChildComponent
|
||||
* }]
|
||||
* }]
|
||||
* path: 'parent',
|
||||
* component: ParentComponent,
|
||||
* children: [{
|
||||
* path: 'list',
|
||||
* component: ListComponent
|
||||
* },{
|
||||
* path: 'child',
|
||||
* component: ChildComponent
|
||||
* }]
|
||||
* }]
|
||||
* ```
|
||||
*
|
||||
* The following `go()` function navigates to the `list` route by
|
||||
@ -67,12 +67,12 @@ export interface NavigationExtras {
|
||||
* ```
|
||||
* @Component({...})
|
||||
* class ChildComponent {
|
||||
* constructor(private router: Router, private route: ActivatedRoute) {}
|
||||
*
|
||||
* go() {
|
||||
* this.router.navigate(['../list'], { relativeTo: this.route });
|
||||
* }
|
||||
* }
|
||||
* constructor(private router: Router, private route: ActivatedRoute) {}
|
||||
*
|
||||
* go() {
|
||||
* this.router.navigate(['../list'], { relativeTo: this.route });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
relativeTo?: ActivatedRoute|null;
|
||||
@ -243,13 +243,13 @@ export type NavigationTransition = {
|
||||
reject: any,
|
||||
promise: Promise<boolean>,
|
||||
source: NavigationTrigger,
|
||||
restoredState: RestoredState | null,
|
||||
restoredState: RestoredState|null,
|
||||
currentSnapshot: RouterStateSnapshot,
|
||||
targetSnapshot: RouterStateSnapshot | null,
|
||||
targetSnapshot: RouterStateSnapshot|null,
|
||||
currentRouterState: RouterState,
|
||||
targetRouterState: RouterState | null,
|
||||
targetRouterState: RouterState|null,
|
||||
guards: Checks,
|
||||
guardsResult: boolean | UrlTree | null,
|
||||
guardsResult: boolean|UrlTree|null,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -273,7 +273,7 @@ function defaultRouterHook(snapshot: RouterStateSnapshot, runExtras: {
|
||||
replaceUrl: boolean,
|
||||
navigationId: number
|
||||
}): Observable<void> {
|
||||
return of (null) as any;
|
||||
return of(null) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,7 +298,7 @@ export class Router {
|
||||
private currentNavigation: Navigation|null = null;
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private locationSubscription !: Subscription;
|
||||
private locationSubscription!: Subscription;
|
||||
private navigationId: number = 0;
|
||||
private configLoader: RouterConfigLoader;
|
||||
private ngModule: NgModuleRef<any>;
|
||||
@ -343,10 +343,10 @@ export class Router {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
hooks: {beforePreactivation: RouterHook, afterPreactivation: RouterHook} = {
|
||||
beforePreactivation: defaultRouterHook,
|
||||
afterPreactivation: defaultRouterHook
|
||||
};
|
||||
hooks: {
|
||||
beforePreactivation: RouterHook,
|
||||
afterPreactivation: RouterHook
|
||||
} = {beforePreactivation: defaultRouterHook, afterPreactivation: defaultRouterHook};
|
||||
|
||||
/**
|
||||
* A strategy for extracting and merging URLs.
|
||||
@ -445,339 +445,358 @@ export class Router {
|
||||
Observable<NavigationTransition> {
|
||||
const eventsSubject = (this.events as Subject<Event>);
|
||||
return transitions.pipe(
|
||||
filter(t => t.id !== 0),
|
||||
filter(t => t.id !== 0),
|
||||
|
||||
// Extract URL
|
||||
map(t => ({
|
||||
...t, extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)
|
||||
} as NavigationTransition)),
|
||||
// Extract URL
|
||||
map(t =>
|
||||
({...t, extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)} as
|
||||
NavigationTransition)),
|
||||
|
||||
// Using switchMap so we cancel executing navigations when a new one comes in
|
||||
switchMap(t => {
|
||||
let completed = false;
|
||||
let errored = false;
|
||||
return of (t).pipe(
|
||||
// Store the Navigation object
|
||||
tap(t => {
|
||||
this.currentNavigation = {
|
||||
id: t.id,
|
||||
initialUrl: t.currentRawUrl,
|
||||
extractedUrl: t.extractedUrl,
|
||||
trigger: t.source,
|
||||
extras: t.extras,
|
||||
previousNavigation: this.lastSuccessfulNavigation ?
|
||||
{...this.lastSuccessfulNavigation, previousNavigation: null} :
|
||||
null
|
||||
};
|
||||
}),
|
||||
switchMap(t => {
|
||||
const urlTransition =
|
||||
!this.navigated || t.extractedUrl.toString() !== this.browserUrlTree.toString();
|
||||
const processCurrentUrl =
|
||||
(this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
|
||||
this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl);
|
||||
// Using switchMap so we cancel executing navigations when a new one comes in
|
||||
switchMap(t => {
|
||||
let completed = false;
|
||||
let errored = false;
|
||||
return of(t).pipe(
|
||||
// Store the Navigation object
|
||||
tap(t => {
|
||||
this.currentNavigation = {
|
||||
id: t.id,
|
||||
initialUrl: t.currentRawUrl,
|
||||
extractedUrl: t.extractedUrl,
|
||||
trigger: t.source,
|
||||
extras: t.extras,
|
||||
previousNavigation: this.lastSuccessfulNavigation ?
|
||||
{...this.lastSuccessfulNavigation, previousNavigation: null} :
|
||||
null
|
||||
};
|
||||
}),
|
||||
switchMap(t => {
|
||||
const urlTransition = !this.navigated ||
|
||||
t.extractedUrl.toString() !== this.browserUrlTree.toString();
|
||||
const processCurrentUrl =
|
||||
(this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
|
||||
this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl);
|
||||
|
||||
if (processCurrentUrl) {
|
||||
return of (t).pipe(
|
||||
// Fire NavigationStart event
|
||||
switchMap(t => {
|
||||
const transition = this.transitions.getValue();
|
||||
eventsSubject.next(new NavigationStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl), t.source, t.restoredState));
|
||||
if (transition !== this.transitions.getValue()) {
|
||||
return EMPTY;
|
||||
}
|
||||
return [t];
|
||||
}),
|
||||
if (processCurrentUrl) {
|
||||
return of(t).pipe(
|
||||
// Fire NavigationStart event
|
||||
switchMap(t => {
|
||||
const transition = this.transitions.getValue();
|
||||
eventsSubject.next(new NavigationStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl), t.source,
|
||||
t.restoredState));
|
||||
if (transition !== this.transitions.getValue()) {
|
||||
return EMPTY;
|
||||
}
|
||||
return [t];
|
||||
}),
|
||||
|
||||
// This delay is required to match old behavior that forced navigation to
|
||||
// always be async
|
||||
switchMap(t => Promise.resolve(t)),
|
||||
// This delay is required to match old behavior that forced navigation
|
||||
// to always be async
|
||||
switchMap(t => Promise.resolve(t)),
|
||||
|
||||
// ApplyRedirects
|
||||
applyRedirects(
|
||||
this.ngModule.injector, this.configLoader, this.urlSerializer,
|
||||
this.config),
|
||||
// ApplyRedirects
|
||||
applyRedirects(
|
||||
this.ngModule.injector, this.configLoader, this.urlSerializer,
|
||||
this.config),
|
||||
|
||||
// Update the currentNavigation
|
||||
tap(t => {
|
||||
this.currentNavigation = {
|
||||
...this.currentNavigation !,
|
||||
finalUrl: t.urlAfterRedirects
|
||||
};
|
||||
}),
|
||||
// Update the currentNavigation
|
||||
tap(t => {
|
||||
this.currentNavigation = {
|
||||
...this.currentNavigation!,
|
||||
finalUrl: t.urlAfterRedirects
|
||||
};
|
||||
}),
|
||||
|
||||
// Recognize
|
||||
recognize(
|
||||
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
|
||||
this.paramsInheritanceStrategy, this.relativeLinkResolution),
|
||||
// Recognize
|
||||
recognize(
|
||||
this.rootComponentType, this.config,
|
||||
(url) => this.serializeUrl(url), this.paramsInheritanceStrategy,
|
||||
this.relativeLinkResolution),
|
||||
|
||||
// Update URL if in `eager` update mode
|
||||
tap(t => {
|
||||
if (this.urlUpdateStrategy === 'eager') {
|
||||
if (!t.extras.skipLocationChange) {
|
||||
this.setBrowserUrl(
|
||||
t.urlAfterRedirects, !!t.extras.replaceUrl, t.id, t.extras.state);
|
||||
}
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
}
|
||||
}),
|
||||
// Update URL if in `eager` update mode
|
||||
tap(t => {
|
||||
if (this.urlUpdateStrategy === 'eager') {
|
||||
if (!t.extras.skipLocationChange) {
|
||||
this.setBrowserUrl(
|
||||
t.urlAfterRedirects, !!t.extras.replaceUrl, t.id,
|
||||
t.extras.state);
|
||||
}
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
}
|
||||
}),
|
||||
|
||||
// Fire RoutesRecognized
|
||||
tap(t => {
|
||||
const routesRecognized = new RoutesRecognized(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !);
|
||||
eventsSubject.next(routesRecognized);
|
||||
}));
|
||||
} else {
|
||||
const processPreviousUrl = urlTransition && this.rawUrlTree &&
|
||||
this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree);
|
||||
/* When the current URL shouldn't be processed, but the previous one was, we
|
||||
* handle this "error condition" by navigating to the previously successful URL,
|
||||
* but leaving the URL intact.*/
|
||||
if (processPreviousUrl) {
|
||||
const {id, extractedUrl, source, restoredState, extras} = t;
|
||||
const navStart = new NavigationStart(
|
||||
id, this.serializeUrl(extractedUrl), source, restoredState);
|
||||
eventsSubject.next(navStart);
|
||||
const targetSnapshot =
|
||||
createEmptyState(extractedUrl, this.rootComponentType).snapshot;
|
||||
// Fire RoutesRecognized
|
||||
tap(t => {
|
||||
const routesRecognized = new RoutesRecognized(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!);
|
||||
eventsSubject.next(routesRecognized);
|
||||
}));
|
||||
} else {
|
||||
const processPreviousUrl = urlTransition && this.rawUrlTree &&
|
||||
this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree);
|
||||
/* When the current URL shouldn't be processed, but the previous one was,
|
||||
* we handle this "error condition" by navigating to the previously
|
||||
* successful URL, but leaving the URL intact.*/
|
||||
if (processPreviousUrl) {
|
||||
const {id, extractedUrl, source, restoredState, extras} = t;
|
||||
const navStart = new NavigationStart(
|
||||
id, this.serializeUrl(extractedUrl), source, restoredState);
|
||||
eventsSubject.next(navStart);
|
||||
const targetSnapshot =
|
||||
createEmptyState(extractedUrl, this.rootComponentType).snapshot;
|
||||
|
||||
return of ({
|
||||
...t,
|
||||
targetSnapshot,
|
||||
urlAfterRedirects: extractedUrl,
|
||||
extras: {...extras, skipLocationChange: false, replaceUrl: false},
|
||||
});
|
||||
} else {
|
||||
/* When neither the current or previous URL can be processed, do nothing other
|
||||
* than update router's internal reference to the current "settled" URL. This
|
||||
* way the next navigation will be coming from the current URL in the browser.
|
||||
*/
|
||||
this.rawUrlTree = t.rawUrl;
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
t.resolve(null);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
}),
|
||||
return of({
|
||||
...t,
|
||||
targetSnapshot,
|
||||
urlAfterRedirects: extractedUrl,
|
||||
extras: {...extras, skipLocationChange: false, replaceUrl: false},
|
||||
});
|
||||
} else {
|
||||
/* When neither the current or previous URL can be processed, do nothing
|
||||
* other than update router's internal reference to the current "settled"
|
||||
* URL. This way the next navigation will be coming from the current URL
|
||||
* in the browser.
|
||||
*/
|
||||
this.rawUrlTree = t.rawUrl;
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
t.resolve(null);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
// Before Preactivation
|
||||
switchTap(t => {
|
||||
const {
|
||||
targetSnapshot,
|
||||
id: navigationId,
|
||||
extractedUrl: appliedUrlTree,
|
||||
rawUrl: rawUrlTree,
|
||||
extras: {skipLocationChange, replaceUrl}
|
||||
} = t;
|
||||
return this.hooks.beforePreactivation(targetSnapshot !, {
|
||||
navigationId,
|
||||
appliedUrlTree,
|
||||
rawUrlTree,
|
||||
skipLocationChange: !!skipLocationChange,
|
||||
replaceUrl: !!replaceUrl,
|
||||
});
|
||||
}),
|
||||
// Before Preactivation
|
||||
switchTap(t => {
|
||||
const {
|
||||
targetSnapshot,
|
||||
id: navigationId,
|
||||
extractedUrl: appliedUrlTree,
|
||||
rawUrl: rawUrlTree,
|
||||
extras: {skipLocationChange, replaceUrl}
|
||||
} = t;
|
||||
return this.hooks.beforePreactivation(targetSnapshot!, {
|
||||
navigationId,
|
||||
appliedUrlTree,
|
||||
rawUrlTree,
|
||||
skipLocationChange: !!skipLocationChange,
|
||||
replaceUrl: !!replaceUrl,
|
||||
});
|
||||
}),
|
||||
|
||||
// --- GUARDS ---
|
||||
tap(t => {
|
||||
const guardsStart = new GuardsCheckStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects),
|
||||
t.targetSnapshot !);
|
||||
this.triggerEvent(guardsStart);
|
||||
}),
|
||||
// --- GUARDS ---
|
||||
tap(t => {
|
||||
const guardsStart = new GuardsCheckStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!);
|
||||
this.triggerEvent(guardsStart);
|
||||
}),
|
||||
|
||||
map(t => ({
|
||||
...t,
|
||||
guards:
|
||||
getAllRouteGuards(t.targetSnapshot !, t.currentSnapshot, this.rootContexts)
|
||||
})),
|
||||
map(t => ({
|
||||
...t,
|
||||
guards: getAllRouteGuards(
|
||||
t.targetSnapshot!, t.currentSnapshot, this.rootContexts)
|
||||
})),
|
||||
|
||||
checkGuards(this.ngModule.injector, (evt: Event) => this.triggerEvent(evt)),
|
||||
tap(t => {
|
||||
if (isUrlTree(t.guardsResult)) {
|
||||
const error: Error&{url?: UrlTree} = navigationCancelingError(
|
||||
`Redirecting to "${this.serializeUrl(t.guardsResult)}"`);
|
||||
error.url = t.guardsResult;
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
checkGuards(this.ngModule.injector, (evt: Event) => this.triggerEvent(evt)),
|
||||
tap(t => {
|
||||
if (isUrlTree(t.guardsResult)) {
|
||||
const error: Error&{url?: UrlTree} = navigationCancelingError(
|
||||
`Redirecting to "${this.serializeUrl(t.guardsResult)}"`);
|
||||
error.url = t.guardsResult;
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
tap(t => {
|
||||
const guardsEnd = new GuardsCheckEnd(
|
||||
t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects),
|
||||
t.targetSnapshot !, !!t.guardsResult);
|
||||
this.triggerEvent(guardsEnd);
|
||||
}),
|
||||
tap(t => {
|
||||
const guardsEnd = new GuardsCheckEnd(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!,
|
||||
!!t.guardsResult);
|
||||
this.triggerEvent(guardsEnd);
|
||||
}),
|
||||
|
||||
filter(t => {
|
||||
if (!t.guardsResult) {
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
const navCancel =
|
||||
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), '');
|
||||
eventsSubject.next(navCancel);
|
||||
t.resolve(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
filter(t => {
|
||||
if (!t.guardsResult) {
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
const navCancel =
|
||||
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), '');
|
||||
eventsSubject.next(navCancel);
|
||||
t.resolve(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
// --- RESOLVE ---
|
||||
switchTap(t => {
|
||||
if (t.guards.canActivateChecks.length) {
|
||||
return of (t).pipe(
|
||||
tap(t => {
|
||||
const resolveStart = new ResolveStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !);
|
||||
this.triggerEvent(resolveStart);
|
||||
}),
|
||||
resolveData(
|
||||
this.paramsInheritanceStrategy,
|
||||
this.ngModule.injector), //
|
||||
tap(t => {
|
||||
const resolveEnd = new ResolveEnd(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !);
|
||||
this.triggerEvent(resolveEnd);
|
||||
}));
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
// --- RESOLVE ---
|
||||
switchTap(t => {
|
||||
if (t.guards.canActivateChecks.length) {
|
||||
return of(t).pipe(
|
||||
tap(t => {
|
||||
const resolveStart = new ResolveStart(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!);
|
||||
this.triggerEvent(resolveStart);
|
||||
}),
|
||||
resolveData(
|
||||
this.paramsInheritanceStrategy,
|
||||
this.ngModule.injector), //
|
||||
tap(t => {
|
||||
const resolveEnd = new ResolveEnd(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot!);
|
||||
this.triggerEvent(resolveEnd);
|
||||
}));
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
|
||||
// --- AFTER PREACTIVATION ---
|
||||
switchTap((t: NavigationTransition) => {
|
||||
const {
|
||||
targetSnapshot,
|
||||
id: navigationId,
|
||||
extractedUrl: appliedUrlTree,
|
||||
rawUrl: rawUrlTree,
|
||||
extras: {skipLocationChange, replaceUrl}
|
||||
} = t;
|
||||
return this.hooks.afterPreactivation(targetSnapshot !, {
|
||||
navigationId,
|
||||
appliedUrlTree,
|
||||
rawUrlTree,
|
||||
skipLocationChange: !!skipLocationChange,
|
||||
replaceUrl: !!replaceUrl,
|
||||
});
|
||||
}),
|
||||
// --- AFTER PREACTIVATION ---
|
||||
switchTap((t: NavigationTransition) => {
|
||||
const {
|
||||
targetSnapshot,
|
||||
id: navigationId,
|
||||
extractedUrl: appliedUrlTree,
|
||||
rawUrl: rawUrlTree,
|
||||
extras: {skipLocationChange, replaceUrl}
|
||||
} = t;
|
||||
return this.hooks.afterPreactivation(targetSnapshot!, {
|
||||
navigationId,
|
||||
appliedUrlTree,
|
||||
rawUrlTree,
|
||||
skipLocationChange: !!skipLocationChange,
|
||||
replaceUrl: !!replaceUrl,
|
||||
});
|
||||
}),
|
||||
|
||||
map((t: NavigationTransition) => {
|
||||
const targetRouterState = createRouterState(
|
||||
this.routeReuseStrategy, t.targetSnapshot !, t.currentRouterState);
|
||||
return ({...t, targetRouterState});
|
||||
}),
|
||||
map((t: NavigationTransition) => {
|
||||
const targetRouterState = createRouterState(
|
||||
this.routeReuseStrategy, t.targetSnapshot!, t.currentRouterState);
|
||||
return ({...t, targetRouterState});
|
||||
}),
|
||||
|
||||
/* Once here, we are about to activate syncronously. The assumption is this will
|
||||
succeed, and user code may read from the Router service. Therefore before
|
||||
activation, we need to update router properties storing the current URL and the
|
||||
RouterState, as well as updated the browser URL. All this should happen *before*
|
||||
activating. */
|
||||
tap((t: NavigationTransition) => {
|
||||
this.currentUrlTree = t.urlAfterRedirects;
|
||||
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl);
|
||||
/* Once here, we are about to activate syncronously. The assumption is this
|
||||
will succeed, and user code may read from the Router service. Therefore
|
||||
before activation, we need to update router properties storing the current
|
||||
URL and the RouterState, as well as updated the browser URL. All this should
|
||||
happen *before* activating. */
|
||||
tap((t: NavigationTransition) => {
|
||||
this.currentUrlTree = t.urlAfterRedirects;
|
||||
this.rawUrlTree =
|
||||
this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl);
|
||||
|
||||
(this as{routerState: RouterState}).routerState = t.targetRouterState !;
|
||||
(this as {routerState: RouterState}).routerState = t.targetRouterState!;
|
||||
|
||||
if (this.urlUpdateStrategy === 'deferred') {
|
||||
if (!t.extras.skipLocationChange) {
|
||||
this.setBrowserUrl(
|
||||
this.rawUrlTree, !!t.extras.replaceUrl, t.id, t.extras.state);
|
||||
}
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
}
|
||||
}),
|
||||
if (this.urlUpdateStrategy === 'deferred') {
|
||||
if (!t.extras.skipLocationChange) {
|
||||
this.setBrowserUrl(
|
||||
this.rawUrlTree, !!t.extras.replaceUrl, t.id, t.extras.state);
|
||||
}
|
||||
this.browserUrlTree = t.urlAfterRedirects;
|
||||
}
|
||||
}),
|
||||
|
||||
activateRoutes(
|
||||
this.rootContexts, this.routeReuseStrategy,
|
||||
(evt: Event) => this.triggerEvent(evt)),
|
||||
activateRoutes(
|
||||
this.rootContexts, this.routeReuseStrategy,
|
||||
(evt: Event) => this.triggerEvent(evt)),
|
||||
|
||||
tap({next() { completed = true; }, complete() { completed = true; }}),
|
||||
finalize(() => {
|
||||
/* When the navigation stream finishes either through error or success, we set the
|
||||
* `completed` or `errored` flag. However, there are some situations where we could
|
||||
* get here without either of those being set. For instance, a redirect during
|
||||
* NavigationStart. Therefore, this is a catch-all to make sure the NavigationCancel
|
||||
* event is fired when a navigation gets cancelled but not caught by other means. */
|
||||
if (!completed && !errored) {
|
||||
// Must reset to current URL tree here to ensure history.state is set. On a fresh
|
||||
// page load, if a new navigation comes in before a successful navigation
|
||||
// completes, there will be nothing in history.state.navigationId. This can cause
|
||||
// sync problems with AngularJS sync code which looks for a value here in order
|
||||
// to determine whether or not to handle a given popstate event or to leave it
|
||||
// to the Angualr router.
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
const navCancel = new NavigationCancel(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
`Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`);
|
||||
eventsSubject.next(navCancel);
|
||||
t.resolve(false);
|
||||
}
|
||||
// currentNavigation should always be reset to null here. If navigation was
|
||||
// successful, lastSuccessfulTransition will have already been set. Therefore we
|
||||
// can safely set currentNavigation to null here.
|
||||
this.currentNavigation = null;
|
||||
}),
|
||||
catchError((e) => {
|
||||
errored = true;
|
||||
/* 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) {
|
||||
// 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. This is
|
||||
// only applicable with initial navigation, so setting `navigated` only when
|
||||
// not redirecting resolves this scenario.
|
||||
this.navigated = true;
|
||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||
}
|
||||
const navCancel =
|
||||
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), e.message);
|
||||
eventsSubject.next(navCancel);
|
||||
tap({
|
||||
next() {
|
||||
completed = true;
|
||||
},
|
||||
complete() {
|
||||
completed = true;
|
||||
}
|
||||
}),
|
||||
finalize(() => {
|
||||
/* When the navigation stream finishes either through error or success, we
|
||||
* set the `completed` or `errored` flag. However, there are some situations
|
||||
* where we could get here without either of those being set. For instance, a
|
||||
* redirect during NavigationStart. Therefore, this is a catch-all to make
|
||||
* sure the NavigationCancel
|
||||
* event is fired when a navigation gets cancelled but not caught by other
|
||||
* means. */
|
||||
if (!completed && !errored) {
|
||||
// Must reset to current URL tree here to ensure history.state is set. On a
|
||||
// fresh page load, if a new navigation comes in before a successful
|
||||
// navigation completes, there will be nothing in
|
||||
// history.state.navigationId. This can cause sync problems with AngularJS
|
||||
// sync code which looks for a value here in order to determine whether or
|
||||
// not to handle a given popstate event or to leave it to the Angualr
|
||||
// router.
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
const navCancel = new NavigationCancel(
|
||||
t.id, this.serializeUrl(t.extractedUrl),
|
||||
`Navigation ID ${t.id} is not equal to the current navigation id ${
|
||||
this.navigationId}`);
|
||||
eventsSubject.next(navCancel);
|
||||
t.resolve(false);
|
||||
}
|
||||
// currentNavigation should always be reset to null here. If navigation was
|
||||
// successful, lastSuccessfulTransition will have already been set. Therefore
|
||||
// we can safely set currentNavigation to null here.
|
||||
this.currentNavigation = null;
|
||||
}),
|
||||
catchError((e) => {
|
||||
errored = true;
|
||||
/* 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) {
|
||||
// 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.
|
||||
// This is only applicable with initial navigation, so setting
|
||||
// `navigated` only when not redirecting resolves this scenario.
|
||||
this.navigated = true;
|
||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||
}
|
||||
const navCancel = new NavigationCancel(
|
||||
t.id, this.serializeUrl(t.extractedUrl), e.message);
|
||||
eventsSubject.next(navCancel);
|
||||
|
||||
// When redirecting, we need to delay resolving the navigation
|
||||
// promise and push it to the redirect navigation
|
||||
if (!redirecting) {
|
||||
t.resolve(false);
|
||||
} else {
|
||||
// setTimeout is required so this navigation finishes with
|
||||
// the return EMPTY below. If it isn't allowed to finish
|
||||
// processing, there can be multiple navigations to the same
|
||||
// URL.
|
||||
setTimeout(() => {
|
||||
const mergedTree = this.urlHandlingStrategy.merge(e.url, this.rawUrlTree);
|
||||
const extras = {
|
||||
skipLocationChange: t.extras.skipLocationChange,
|
||||
replaceUrl: this.urlUpdateStrategy === 'eager'
|
||||
};
|
||||
// When redirecting, we need to delay resolving the navigation
|
||||
// promise and push it to the redirect navigation
|
||||
if (!redirecting) {
|
||||
t.resolve(false);
|
||||
} else {
|
||||
// setTimeout is required so this navigation finishes with
|
||||
// the return EMPTY below. If it isn't allowed to finish
|
||||
// processing, there can be multiple navigations to the same
|
||||
// URL.
|
||||
setTimeout(() => {
|
||||
const mergedTree =
|
||||
this.urlHandlingStrategy.merge(e.url, this.rawUrlTree);
|
||||
const extras = {
|
||||
skipLocationChange: t.extras.skipLocationChange,
|
||||
replaceUrl: this.urlUpdateStrategy === 'eager'
|
||||
};
|
||||
|
||||
return this.scheduleNavigation(
|
||||
mergedTree, 'imperative', null, extras,
|
||||
{resolve: t.resolve, reject: t.reject, promise: t.promise});
|
||||
}, 0);
|
||||
}
|
||||
return this.scheduleNavigation(
|
||||
mergedTree, 'imperative', null, extras,
|
||||
{resolve: t.resolve, reject: t.reject, promise: t.promise});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/* All other errors should reset to the router's internal URL reference to the
|
||||
* pre-error state. */
|
||||
} else {
|
||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||
const navError = new NavigationError(t.id, this.serializeUrl(t.extractedUrl), e);
|
||||
eventsSubject.next(navError);
|
||||
try {
|
||||
t.resolve(this.errorHandler(e));
|
||||
} catch (ee) {
|
||||
t.reject(ee);
|
||||
}
|
||||
}
|
||||
return EMPTY;
|
||||
}));
|
||||
// TODO(jasonaden): remove cast once g3 is on updated TypeScript
|
||||
})) as any as Observable<NavigationTransition>;
|
||||
/* All other errors should reset to the router's internal URL reference to
|
||||
* the pre-error state. */
|
||||
} else {
|
||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||
const navError =
|
||||
new NavigationError(t.id, this.serializeUrl(t.extractedUrl), e);
|
||||
eventsSubject.next(navError);
|
||||
try {
|
||||
t.resolve(this.errorHandler(e));
|
||||
} catch (ee) {
|
||||
t.reject(ee);
|
||||
}
|
||||
}
|
||||
return EMPTY;
|
||||
}));
|
||||
// TODO(jasonaden): remove cast once g3 is on updated TypeScript
|
||||
})) as any as Observable<NavigationTransition>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -828,20 +847,27 @@ export class Router {
|
||||
// Navigations coming from Angular router have a navigationId state property. When this
|
||||
// exists, restore the state.
|
||||
const state = change.state && change.state.navigationId ? change.state : null;
|
||||
setTimeout(
|
||||
() => { this.scheduleNavigation(rawUrlTree, source, state, {replaceUrl: true}); }, 0);
|
||||
setTimeout(() => {
|
||||
this.scheduleNavigation(rawUrlTree, source, state, {replaceUrl: true});
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** The current URL. */
|
||||
get url(): string { return this.serializeUrl(this.currentUrlTree); }
|
||||
get url(): string {
|
||||
return this.serializeUrl(this.currentUrlTree);
|
||||
}
|
||||
|
||||
/** The current Navigation object if one exists */
|
||||
getCurrentNavigation(): Navigation|null { return this.currentNavigation; }
|
||||
getCurrentNavigation(): Navigation|null {
|
||||
return this.currentNavigation;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
triggerEvent(event: Event): void { (this.events as Subject<Event>).next(event); }
|
||||
triggerEvent(event: Event): void {
|
||||
(this.events as Subject<Event>).next(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the configuration used for navigation and generating links.
|
||||
@ -867,13 +893,15 @@ export class Router {
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
ngOnDestroy(): void { this.dispose(); }
|
||||
ngOnDestroy(): void {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
/** Disposes of the router. */
|
||||
dispose(): void {
|
||||
if (this.locationSubscription) {
|
||||
this.locationSubscription.unsubscribe();
|
||||
this.locationSubscription = null !;
|
||||
this.locationSubscription = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -923,8 +951,14 @@ export class Router {
|
||||
* ```
|
||||
*/
|
||||
createUrlTree(commands: any[], navigationExtras: NavigationExtras = {}): UrlTree {
|
||||
const {relativeTo, queryParams, fragment,
|
||||
preserveQueryParams, queryParamsHandling, preserveFragment} = navigationExtras;
|
||||
const {
|
||||
relativeTo,
|
||||
queryParams,
|
||||
fragment,
|
||||
preserveQueryParams,
|
||||
queryParamsHandling,
|
||||
preserveFragment
|
||||
} = navigationExtras;
|
||||
if (isDevMode() && preserveQueryParams && <any>console && <any>console.warn) {
|
||||
console.warn('preserveQueryParams is deprecated, use queryParamsHandling instead.');
|
||||
}
|
||||
@ -948,7 +982,7 @@ export class Router {
|
||||
if (q !== null) {
|
||||
q = this.removeEmptyProps(q);
|
||||
}
|
||||
return createUrlTree(a, this.currentUrlTree, commands, q !, f !);
|
||||
return createUrlTree(a, this.currentUrlTree, commands, q!, f!);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1019,7 +1053,9 @@ export class Router {
|
||||
}
|
||||
|
||||
/** Serializes a `UrlTree` into a string */
|
||||
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
|
||||
serializeUrl(url: UrlTree): string {
|
||||
return this.urlSerializer.serialize(url);
|
||||
}
|
||||
|
||||
/** Parses a string into a `UrlTree` */
|
||||
parseUrl(url: string): UrlTree {
|
||||
@ -1064,7 +1100,9 @@ export class Router {
|
||||
this.currentNavigation = null;
|
||||
t.resolve(true);
|
||||
},
|
||||
e => { this.console.warn(`Unhandled Navigation Error: `); });
|
||||
e => {
|
||||
this.console.warn(`Unhandled Navigation Error: `);
|
||||
});
|
||||
}
|
||||
|
||||
private scheduleNavigation(
|
||||
@ -1116,14 +1154,21 @@ export class Router {
|
||||
source,
|
||||
restoredState,
|
||||
currentUrlTree: this.currentUrlTree,
|
||||
currentRawUrl: this.rawUrlTree, rawUrl, extras, resolve, reject, promise,
|
||||
currentRawUrl: this.rawUrlTree,
|
||||
rawUrl,
|
||||
extras,
|
||||
resolve,
|
||||
reject,
|
||||
promise,
|
||||
currentSnapshot: this.routerState.snapshot,
|
||||
currentRouterState: this.routerState
|
||||
});
|
||||
|
||||
// Make sure that the error is propagated even though `processNavigations` catch
|
||||
// handler does not rethrow
|
||||
return promise.catch((e: any) => { return Promise.reject(e); });
|
||||
return promise.catch((e: any) => {
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
||||
private setBrowserUrl(
|
||||
@ -1139,7 +1184,7 @@ export class Router {
|
||||
}
|
||||
|
||||
private resetStateAndUrl(storedState: RouterState, storedUrl: UrlTree, rawUrl: UrlTree): void {
|
||||
(this as{routerState: RouterState}).routerState = storedState;
|
||||
(this as {routerState: RouterState}).routerState = storedState;
|
||||
this.currentUrlTree = storedUrl;
|
||||
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
|
@ -7,8 +7,9 @@
|
||||
*/
|
||||
|
||||
import {Compiler, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
|
||||
import {Observable, from, of } from 'rxjs';
|
||||
import {from, Observable, of} from 'rxjs';
|
||||
import {map, mergeMap} from 'rxjs/operators';
|
||||
|
||||
import {LoadChildren, LoadedRouterConfig, Route, standardizeConfig} from './config';
|
||||
import {flatten, wrapIntoObservable} from './utils/collection';
|
||||
|
||||
@ -30,7 +31,7 @@ export class RouterConfigLoader {
|
||||
this.onLoadStartListener(route);
|
||||
}
|
||||
|
||||
const moduleFactory$ = this.loadModuleFactory(route.loadChildren !);
|
||||
const moduleFactory$ = this.loadModuleFactory(route.loadChildren!);
|
||||
|
||||
return moduleFactory$.pipe(map((factory: NgModuleFactory<any>) => {
|
||||
if (this.onLoadEndListener) {
|
||||
@ -50,7 +51,7 @@ export class RouterConfigLoader {
|
||||
} else {
|
||||
return wrapIntoObservable(loadChildren()).pipe(mergeMap((t: any) => {
|
||||
if (t instanceof NgModuleFactory) {
|
||||
return of (t);
|
||||
return of(t);
|
||||
} else {
|
||||
return from(this.compiler.compileModuleAsync(t));
|
||||
}
|
||||
|
@ -6,9 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_BASE_HREF, HashLocationStrategy, LOCATION_INITIALIZED, Location, LocationStrategy, PathLocationStrategy, PlatformLocation, ViewportScroller, ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {APP_BASE_HREF, HashLocationStrategy, Location, LOCATION_INITIALIZED, LocationStrategy, PathLocationStrategy, PlatformLocation, ViewportScroller, ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, Inject, Injectable, InjectionToken, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core';
|
||||
import {Subject, of } from 'rxjs';
|
||||
import {of, Subject} from 'rxjs';
|
||||
|
||||
import {EmptyOutletComponent} from './components/empty_outlet';
|
||||
import {Route, Routes} from './config';
|
||||
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
@ -136,7 +137,7 @@ export class RouterModule {
|
||||
* @param routes An array of `Route` objects that define the navigation paths for the application.
|
||||
* @param config An `ExtraOptions` configuration object that controls how navigation is performed.
|
||||
* @return The new router module.
|
||||
*/
|
||||
*/
|
||||
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule> {
|
||||
return {
|
||||
ngModule: RouterModule,
|
||||
@ -152,9 +153,8 @@ export class RouterModule {
|
||||
{
|
||||
provide: LocationStrategy,
|
||||
useFactory: provideLocationStrategy,
|
||||
deps: [
|
||||
PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], ROUTER_CONFIGURATION
|
||||
]
|
||||
deps:
|
||||
[PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], ROUTER_CONFIGURATION]
|
||||
},
|
||||
{
|
||||
provide: RouterScroller,
|
||||
@ -236,8 +236,9 @@ export function provideRoutes(routes: Routes): any {
|
||||
* the root component gets created. Use if there is a reason to have
|
||||
* more control over when the router starts its initial navigation due to some complex
|
||||
* initialization logic.
|
||||
* * 'legacy_enabled'- (Default, for compatibility.) The initial navigation starts after the root component has been created.
|
||||
* The bootstrap is not blocked until the initial navigation is complete. @deprecated
|
||||
* * 'legacy_enabled'- (Default, for compatibility.) The initial navigation starts after the root
|
||||
* component has been created. The bootstrap is not blocked until the initial navigation is
|
||||
* complete. @deprecated
|
||||
* * 'legacy_disabled'- The initial navigation is not performed. The location listener is set up
|
||||
* after the root component gets created. @deprecated since v4
|
||||
* * `true` - same as 'legacy_enabled'. @deprecated since v4
|
||||
@ -249,8 +250,7 @@ export function provideRoutes(routes: Routes): any {
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type InitialNavigation =
|
||||
true | false | 'enabled' | 'disabled' | 'legacy_enabled' | 'legacy_disabled';
|
||||
export type InitialNavigation = true|false|'enabled'|'disabled'|'legacy_enabled'|'legacy_disabled';
|
||||
|
||||
/**
|
||||
* A set of configuration options for a router module, provided in the
|
||||
@ -506,7 +506,7 @@ export class RouterInitializer {
|
||||
appInitializer(): Promise<any> {
|
||||
const p: Promise<any> = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
|
||||
return p.then(() => {
|
||||
let resolve: Function = null !;
|
||||
let resolve: Function = null!;
|
||||
const res = new Promise(r => resolve = r);
|
||||
const router = this.injector.get(Router);
|
||||
const opts = this.injector.get(ROUTER_CONFIGURATION);
|
||||
@ -528,7 +528,7 @@ export class RouterInitializer {
|
||||
|
||||
// subsequent navigations should not be delayed
|
||||
} else {
|
||||
return of (null) as any;
|
||||
return of(null) as any;
|
||||
}
|
||||
};
|
||||
router.initialNavigation();
|
||||
@ -561,7 +561,7 @@ export class RouterInitializer {
|
||||
preloader.setUpPreloading();
|
||||
routerScroller.init();
|
||||
router.resetRootComponentType(ref.componentTypes[0]);
|
||||
this.resultOfPreactivationDone.next(null !);
|
||||
this.resultOfPreactivationDone.next(null!);
|
||||
this.resultOfPreactivationDone.complete();
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,9 @@ export class ChildrenOutletContexts {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
onOutletReAttached(contexts: Map<string, OutletContext>) { this.contexts = contexts; }
|
||||
onOutletReAttached(contexts: Map<string, OutletContext>) {
|
||||
this.contexts = contexts;
|
||||
}
|
||||
|
||||
getOrCreateContext(childName: string): OutletContext {
|
||||
let context = this.getContext(childName);
|
||||
@ -76,5 +78,7 @@ export class ChildrenOutletContexts {
|
||||
return context;
|
||||
}
|
||||
|
||||
getContext(childName: string): OutletContext|null { return this.contexts.get(childName) || null; }
|
||||
getContext(childName: string): OutletContext|null {
|
||||
return this.contexts.get(childName) || null;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
*@license
|
||||
*Copyright Google Inc. 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
|
||||
*/
|
||||
*@license
|
||||
*Copyright Google Inc. 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 {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef, OnDestroy} from '@angular/core';
|
||||
import {Observable, Subscription, from, of } from 'rxjs';
|
||||
import {from, Observable, of, Subscription} from 'rxjs';
|
||||
import {catchError, concatMap, filter, map, mergeAll, mergeMap} from 'rxjs/operators';
|
||||
|
||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||
@ -40,7 +40,7 @@ export abstract class PreloadingStrategy {
|
||||
*/
|
||||
export class PreloadAllModules implements PreloadingStrategy {
|
||||
preload(route: Route, fn: () => Observable<any>): Observable<any> {
|
||||
return fn().pipe(catchError(() => of (null)));
|
||||
return fn().pipe(catchError(() => of(null)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,9 @@ export class PreloadAllModules implements PreloadingStrategy {
|
||||
* @publicApi
|
||||
*/
|
||||
export class NoPreloading implements PreloadingStrategy {
|
||||
preload(route: Route, fn: () => Observable<any>): Observable<any> { return of (null); }
|
||||
preload(route: Route, fn: () => Observable<any>): Observable<any> {
|
||||
return of(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +75,7 @@ export class NoPreloading implements PreloadingStrategy {
|
||||
export class RouterPreloader implements OnDestroy {
|
||||
private loader: RouterConfigLoader;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private subscription !: Subscription;
|
||||
private subscription!: Subscription;
|
||||
|
||||
constructor(
|
||||
private router: Router, moduleLoader: NgModuleFactoryLoader, compiler: Compiler,
|
||||
@ -99,7 +101,9 @@ export class RouterPreloader implements OnDestroy {
|
||||
// TODO(jasonaden): This class relies on code external to the class to call setUpPreloading. If
|
||||
// this hasn't been done, ngOnDestroy will fail as this.subscription will be undefined. This
|
||||
// should be refactored.
|
||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
private processRoutes(ngModule: NgModuleRef<any>, routes: Routes): Observable<void> {
|
||||
const res: Observable<any>[] = [];
|
||||
|
@ -15,9 +15,9 @@ import {Router} from './router';
|
||||
|
||||
export class RouterScroller implements OnDestroy {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private routerEventsSubscription !: Unsubscribable;
|
||||
private routerEventsSubscription!: Unsubscribable;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private scrollEventsSubscription !: Unsubscribable;
|
||||
private scrollEventsSubscription!: Unsubscribable;
|
||||
|
||||
private lastId = 0;
|
||||
private lastSource: 'imperative'|'popstate'|'hashchange'|undefined = 'imperative';
|
||||
@ -27,7 +27,7 @@ export class RouterScroller implements OnDestroy {
|
||||
constructor(
|
||||
private router: Router,
|
||||
/** @docsNotRequired */ public readonly viewportScroller: ViewportScroller, private options: {
|
||||
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top',
|
||||
scrollPositionRestoration?: 'disabled'|'enabled'|'top',
|
||||
anchorScrolling?: 'disabled'|'enabled'
|
||||
} = {}) {
|
||||
// Default both options to 'disabled'
|
||||
|
@ -11,8 +11,8 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
import {Data, ResolveData, Route} from './config';
|
||||
import {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from './url_tree';
|
||||
import {convertToParamMap, ParamMap, Params, PRIMARY_OUTLET} from './shared';
|
||||
import {equalSegments, UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {shallowEqual, shallowEqualArrays} from './utils/collection';
|
||||
import {Tree, TreeNode} from './utils/tree';
|
||||
|
||||
@ -57,10 +57,12 @@ export class RouterState extends Tree<ActivatedRoute> {
|
||||
setRouterState(<RouterState>this, root);
|
||||
}
|
||||
|
||||
toString(): string { return this.snapshot.toString(); }
|
||||
toString(): string {
|
||||
return this.snapshot.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyState(urlTree: UrlTree, rootComponent: Type<any>| null): RouterState {
|
||||
export function createEmptyState(urlTree: UrlTree, rootComponent: Type<any>|null): RouterState {
|
||||
const snapshot = createEmptyStateSnapshot(urlTree, rootComponent);
|
||||
const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]);
|
||||
const emptyParams = new BehaviorSubject({});
|
||||
@ -75,7 +77,7 @@ export function createEmptyState(urlTree: UrlTree, rootComponent: Type<any>| nul
|
||||
}
|
||||
|
||||
export function createEmptyStateSnapshot(
|
||||
urlTree: UrlTree, rootComponent: Type<any>| null): RouterStateSnapshot {
|
||||
urlTree: UrlTree, rootComponent: Type<any>|null): RouterStateSnapshot {
|
||||
const emptyParams = {};
|
||||
const emptyData = {};
|
||||
const emptyQueryParams = {};
|
||||
@ -98,15 +100,15 @@ export function createEmptyStateSnapshot(
|
||||
*/
|
||||
export class ActivatedRoute {
|
||||
/** The current snapshot of this route */
|
||||
snapshot !: ActivatedRouteSnapshot;
|
||||
snapshot!: ActivatedRouteSnapshot;
|
||||
/** @internal */
|
||||
_futureSnapshot: ActivatedRouteSnapshot;
|
||||
/** @internal */
|
||||
_routerState !: RouterState;
|
||||
_routerState!: RouterState;
|
||||
/** @internal */
|
||||
_paramMap !: Observable<ParamMap>;
|
||||
_paramMap!: Observable<ParamMap>;
|
||||
/** @internal */
|
||||
_queryParamMap !: Observable<ParamMap>;
|
||||
_queryParamMap!: Observable<ParamMap>;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
@ -129,26 +131,40 @@ export class ActivatedRoute {
|
||||
}
|
||||
|
||||
/** The configuration used to match this route. */
|
||||
get routeConfig(): Route|null { return this._futureSnapshot.routeConfig; }
|
||||
get routeConfig(): Route|null {
|
||||
return this._futureSnapshot.routeConfig;
|
||||
}
|
||||
|
||||
/** The root of the router state. */
|
||||
get root(): ActivatedRoute { return this._routerState.root; }
|
||||
get root(): ActivatedRoute {
|
||||
return this._routerState.root;
|
||||
}
|
||||
|
||||
/** The parent of this route in the router state tree. */
|
||||
get parent(): ActivatedRoute|null { return this._routerState.parent(this); }
|
||||
get parent(): ActivatedRoute|null {
|
||||
return this._routerState.parent(this);
|
||||
}
|
||||
|
||||
/** The first child of this route in the router state tree. */
|
||||
get firstChild(): ActivatedRoute|null { return this._routerState.firstChild(this); }
|
||||
get firstChild(): ActivatedRoute|null {
|
||||
return this._routerState.firstChild(this);
|
||||
}
|
||||
|
||||
/** The children of this route in the router state tree. */
|
||||
get children(): ActivatedRoute[] { return this._routerState.children(this); }
|
||||
get children(): ActivatedRoute[] {
|
||||
return this._routerState.children(this);
|
||||
}
|
||||
|
||||
/** The path from the root of the router state tree to this route. */
|
||||
get pathFromRoot(): ActivatedRoute[] { return this._routerState.pathFromRoot(this); }
|
||||
get pathFromRoot(): ActivatedRoute[] {
|
||||
return this._routerState.pathFromRoot(this);
|
||||
}
|
||||
|
||||
/** An Observable that contains a map of the required and optional parameters
|
||||
/**
|
||||
* An Observable that contains a map of the required and optional parameters
|
||||
* specific to the route.
|
||||
* The map supports retrieving single and multiple values from the same parameter. */
|
||||
* The map supports retrieving single and multiple values from the same parameter.
|
||||
*/
|
||||
get paramMap(): Observable<ParamMap> {
|
||||
if (!this._paramMap) {
|
||||
this._paramMap = this.params.pipe(map((p: Params): ParamMap => convertToParamMap(p)));
|
||||
@ -173,7 +189,7 @@ export class ActivatedRoute {
|
||||
}
|
||||
}
|
||||
|
||||
export type ParamsInheritanceStrategy = 'emptyOnly' | 'always';
|
||||
export type ParamsInheritanceStrategy = 'emptyOnly'|'always';
|
||||
|
||||
/** @internal */
|
||||
export type Inherited = {
|
||||
@ -257,16 +273,16 @@ export class ActivatedRouteSnapshot {
|
||||
_resolve: ResolveData;
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_resolvedData !: Data;
|
||||
_resolvedData!: Data;
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_routerState !: RouterStateSnapshot;
|
||||
_routerState!: RouterStateSnapshot;
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_paramMap !: ParamMap;
|
||||
_paramMap!: ParamMap;
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_queryParamMap !: ParamMap;
|
||||
_queryParamMap!: ParamMap;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
@ -292,19 +308,29 @@ export class ActivatedRouteSnapshot {
|
||||
}
|
||||
|
||||
/** The root of the router state */
|
||||
get root(): ActivatedRouteSnapshot { return this._routerState.root; }
|
||||
get root(): ActivatedRouteSnapshot {
|
||||
return this._routerState.root;
|
||||
}
|
||||
|
||||
/** The parent of this route in the router state tree */
|
||||
get parent(): ActivatedRouteSnapshot|null { return this._routerState.parent(this); }
|
||||
get parent(): ActivatedRouteSnapshot|null {
|
||||
return this._routerState.parent(this);
|
||||
}
|
||||
|
||||
/** The first child of this route in the router state tree */
|
||||
get firstChild(): ActivatedRouteSnapshot|null { return this._routerState.firstChild(this); }
|
||||
get firstChild(): ActivatedRouteSnapshot|null {
|
||||
return this._routerState.firstChild(this);
|
||||
}
|
||||
|
||||
/** The children of this route in the router state tree */
|
||||
get children(): ActivatedRouteSnapshot[] { return this._routerState.children(this); }
|
||||
get children(): ActivatedRouteSnapshot[] {
|
||||
return this._routerState.children(this);
|
||||
}
|
||||
|
||||
/** The path from the root of the router state tree to this route */
|
||||
get pathFromRoot(): ActivatedRouteSnapshot[] { return this._routerState.pathFromRoot(this); }
|
||||
get pathFromRoot(): ActivatedRouteSnapshot[] {
|
||||
return this._routerState.pathFromRoot(this);
|
||||
}
|
||||
|
||||
get paramMap(): ParamMap {
|
||||
if (!this._paramMap) {
|
||||
@ -363,10 +389,12 @@ export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
|
||||
setRouterState(<RouterStateSnapshot>this, root);
|
||||
}
|
||||
|
||||
toString(): string { return serializeNode(this._root); }
|
||||
toString(): string {
|
||||
return serializeNode(this._root);
|
||||
}
|
||||
}
|
||||
|
||||
function setRouterState<U, T extends{_routerState: U}>(state: U, node: TreeNode<T>): void {
|
||||
function setRouterState<U, T extends {_routerState: U}>(state: U, node: TreeNode<T>): void {
|
||||
node.value._routerState = state;
|
||||
node.children.forEach(c => setRouterState(state, c));
|
||||
}
|
||||
@ -416,5 +444,5 @@ export function equalParamsAndUrlSegments(
|
||||
const parentsMismatch = !a.parent !== !b.parent;
|
||||
|
||||
return equalUrlParams && !parentsMismatch &&
|
||||
(!a.parent || equalParamsAndUrlSegments(a.parent, b.parent !));
|
||||
(!a.parent || equalParamsAndUrlSegments(a.parent, b.parent!));
|
||||
}
|
||||
|
@ -69,9 +69,13 @@ export interface ParamMap {
|
||||
class ParamsAsMap implements ParamMap {
|
||||
private params: Params;
|
||||
|
||||
constructor(params: Params) { this.params = params || {}; }
|
||||
constructor(params: Params) {
|
||||
this.params = params || {};
|
||||
}
|
||||
|
||||
has(name: string): boolean { return this.params.hasOwnProperty(name); }
|
||||
has(name: string): boolean {
|
||||
return this.params.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
get(name: string): string|null {
|
||||
if (this.has(name)) {
|
||||
@ -91,7 +95,9 @@ class ParamsAsMap implements ParamMap {
|
||||
return [];
|
||||
}
|
||||
|
||||
get keys(): string[] { return Object.keys(this.params); }
|
||||
get keys(): string[] {
|
||||
return Object.keys(this.params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,7 +126,7 @@ export function isNavigationCancelingError(error: Error) {
|
||||
// Matches the route configuration (`route`) against the actual URL (`segments`).
|
||||
export function defaultUrlMatcher(
|
||||
segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult|null {
|
||||
const parts = route.path !.split('/');
|
||||
const parts = route.path!.split('/');
|
||||
|
||||
if (parts.length > segments.length) {
|
||||
// The actual URL is shorter than the config, no match
|
||||
|
@ -42,7 +42,13 @@ export abstract class UrlHandlingStrategy {
|
||||
* @publicApi
|
||||
*/
|
||||
export class DefaultUrlHandlingStrategy implements UrlHandlingStrategy {
|
||||
shouldProcessUrl(url: UrlTree): boolean { return true; }
|
||||
extract(url: UrlTree): UrlTree { return url; }
|
||||
merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree { return newUrlPart; }
|
||||
shouldProcessUrl(url: UrlTree): boolean {
|
||||
return true;
|
||||
}
|
||||
extract(url: UrlTree): UrlTree {
|
||||
return url;
|
||||
}
|
||||
merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree {
|
||||
return newUrlPart;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from './shared';
|
||||
import {convertToParamMap, ParamMap, Params, PRIMARY_OUTLET} from './shared';
|
||||
import {equalArraysOrString, forEach, shallowEqual} from './utils/collection';
|
||||
|
||||
export function createEmptyUrlTree() {
|
||||
@ -106,7 +106,7 @@ function containsSegmentGroupHelper(
|
||||
export class UrlTree {
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_queryParamMap !: ParamMap;
|
||||
_queryParamMap!: ParamMap;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
@ -125,7 +125,9 @@ export class UrlTree {
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string { return DEFAULT_SERIALIZER.serialize(this); }
|
||||
toString(): string {
|
||||
return DEFAULT_SERIALIZER.serialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,10 +142,10 @@ export class UrlTree {
|
||||
export class UrlSegmentGroup {
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_sourceSegment !: UrlSegmentGroup;
|
||||
_sourceSegment!: UrlSegmentGroup;
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_segmentIndexShift !: number;
|
||||
_segmentIndexShift!: number;
|
||||
/** The parent node in the url tree */
|
||||
parent: UrlSegmentGroup|null = null;
|
||||
|
||||
@ -156,13 +158,19 @@ export class UrlSegmentGroup {
|
||||
}
|
||||
|
||||
/** Whether the segment has child segments */
|
||||
hasChildren(): boolean { return this.numberOfChildren > 0; }
|
||||
hasChildren(): boolean {
|
||||
return this.numberOfChildren > 0;
|
||||
}
|
||||
|
||||
/** Number of child segments */
|
||||
get numberOfChildren(): number { return Object.keys(this.children).length; }
|
||||
get numberOfChildren(): number {
|
||||
return Object.keys(this.children).length;
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string { return serializePaths(this); }
|
||||
toString(): string {
|
||||
return serializePaths(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -195,7 +203,7 @@ export class UrlSegmentGroup {
|
||||
export class UrlSegment {
|
||||
/** @internal */
|
||||
// TODO(issue/24571): remove '!'.
|
||||
_parameterMap !: ParamMap;
|
||||
_parameterMap!: ParamMap;
|
||||
|
||||
constructor(
|
||||
/** The path part of a URL segment */
|
||||
@ -212,7 +220,9 @@ export class UrlSegment {
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
toString(): string { return serializePath(this); }
|
||||
toString(): string {
|
||||
return serializePath(this);
|
||||
}
|
||||
}
|
||||
|
||||
export function equalSegments(as: UrlSegment[], bs: UrlSegment[]): boolean {
|
||||
@ -291,7 +301,7 @@ export class DefaultUrlSerializer implements UrlSerializer {
|
||||
const segment = `/${serializeSegment(tree.root, true)}`;
|
||||
const query = serializeQueryParams(tree.queryParams);
|
||||
const fragment =
|
||||
typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment !)}` : '';
|
||||
typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment!)}` : '';
|
||||
|
||||
return `${segment}${query}${fragment}`;
|
||||
}
|
||||
@ -329,7 +339,6 @@ function serializeSegment(segment: UrlSegmentGroup, root: boolean): string {
|
||||
}
|
||||
|
||||
return [`${k}:${serializeSegment(v, false)}`];
|
||||
|
||||
});
|
||||
|
||||
return `${serializePaths(segment)}/(${children.join('//')})`;
|
||||
@ -409,7 +418,7 @@ function serializeQueryParams(params: {[key: string]: any}): string {
|
||||
`${encodeUriQuery(name)}=${encodeUriQuery(value)}`;
|
||||
});
|
||||
|
||||
return strParams.length ? `?${strParams.join("&")}` : '';
|
||||
return strParams.length ? `?${strParams.join('&')}` : '';
|
||||
}
|
||||
|
||||
const SEGMENT_RE = /^[^\/()?;=#]+/;
|
||||
@ -435,7 +444,9 @@ function matchUrlQueryParamValue(str: string): string {
|
||||
class UrlParser {
|
||||
private remaining: string;
|
||||
|
||||
constructor(private url: string) { this.remaining = url; }
|
||||
constructor(private url: string) {
|
||||
this.remaining = url;
|
||||
}
|
||||
|
||||
parseRootSegment(): UrlSegmentGroup {
|
||||
this.consumeOptional('/');
|
||||
@ -584,7 +595,7 @@ class UrlParser {
|
||||
throw new Error(`Cannot parse url '${this.url}'`);
|
||||
}
|
||||
|
||||
let outletName: string = undefined !;
|
||||
let outletName: string = undefined!;
|
||||
if (path.indexOf(':') > -1) {
|
||||
outletName = path.substr(0, path.indexOf(':'));
|
||||
this.capture(outletName);
|
||||
@ -602,7 +613,9 @@ class UrlParser {
|
||||
return segments;
|
||||
}
|
||||
|
||||
private peekStartsWith(str: string): boolean { return this.remaining.startsWith(str); }
|
||||
private peekStartsWith(str: string): boolean {
|
||||
return this.remaining.startsWith(str);
|
||||
}
|
||||
|
||||
// Consumes the prefix when it is present and returns whether it has been consumed
|
||||
private consumeOptional(str: string): boolean {
|
||||
|
@ -7,10 +7,10 @@
|
||||
*/
|
||||
|
||||
import {NgModuleFactory, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
|
||||
import {Observable, from, of } from 'rxjs';
|
||||
import {from, Observable, of} from 'rxjs';
|
||||
import {concatAll, last as lastValue, map} from 'rxjs/operators';
|
||||
|
||||
import {PRIMARY_OUTLET, Params} from '../shared';
|
||||
import {Params, PRIMARY_OUTLET} from '../shared';
|
||||
|
||||
export function shallowEqualArrays(a: any[], b: any[]): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
@ -43,7 +43,7 @@ export function shallowEqual(a: Params, b: Params): boolean {
|
||||
/**
|
||||
* Test equality for arrays of strings or a string.
|
||||
*/
|
||||
export function equalArraysOrString(a: string | string[], b: string | string[]) {
|
||||
export function equalArraysOrString(a: string|string[], b: string|string[]) {
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length != b.length) return false;
|
||||
return a.every(aItem => b.indexOf(aItem) > -1);
|
||||
@ -84,7 +84,7 @@ export function forEach<K, V>(map: {[key: string]: V}, callback: (v: V, k: strin
|
||||
export function waitForMap<A, B>(
|
||||
obj: {[k: string]: A}, fn: (k: string, a: A) => Observable<B>): Observable<{[k: string]: B}> {
|
||||
if (Object.keys(obj).length === 0) {
|
||||
return of ({});
|
||||
return of({});
|
||||
}
|
||||
|
||||
const waitHead: Observable<B>[] = [];
|
||||
@ -103,11 +103,11 @@ export function waitForMap<A, B>(
|
||||
// Closure compiler has problem with using spread operator here. So we use "Array.concat".
|
||||
// Note that we also need to cast the new promise because TypeScript cannot infer the type
|
||||
// when calling the "of" function through "Function.apply"
|
||||
return (of .apply(null, waitHead.concat(waitTail)) as Observable<Observable<B>>)
|
||||
return (of.apply(null, waitHead.concat(waitTail)) as Observable<Observable<B>>)
|
||||
.pipe(concatAll(), lastValue(), map(() => res));
|
||||
}
|
||||
|
||||
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> {
|
||||
export function wrapIntoObservable<T>(value: T|Promise<T>|Observable<T>): Observable<T> {
|
||||
if (isObservable(value)) {
|
||||
return value;
|
||||
}
|
||||
@ -119,5 +119,5 @@ export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Obs
|
||||
return from(Promise.resolve(value));
|
||||
}
|
||||
|
||||
return of (value);
|
||||
return of(value);
|
||||
}
|
||||
|
@ -10,9 +10,13 @@ export class Tree<T> {
|
||||
/** @internal */
|
||||
_root: TreeNode<T>;
|
||||
|
||||
constructor(root: TreeNode<T>) { this._root = root; }
|
||||
constructor(root: TreeNode<T>) {
|
||||
this._root = root;
|
||||
}
|
||||
|
||||
get root(): T { return this._root.value; }
|
||||
get root(): T {
|
||||
return this._root.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -52,7 +56,9 @@ export class Tree<T> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
pathFromRoot(t: T): T[] { return findPath(t, this._root).map(s => s.value); }
|
||||
pathFromRoot(t: T): T[] {
|
||||
return findPath(t, this._root).map(s => s.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,11 +92,13 @@ function findPath<T>(value: T, node: TreeNode<T>): TreeNode<T>[] {
|
||||
export class TreeNode<T> {
|
||||
constructor(public value: T, public children: TreeNode<T>[]) {}
|
||||
|
||||
toString(): string { return `TreeNode(${this.value})`; }
|
||||
toString(): string {
|
||||
return `TreeNode(${this.value})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the list of T indexed by outlet name
|
||||
export function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) {
|
||||
export function nodeChildrenAsMap<T extends {outlet: string}>(node: TreeNode<T>|null) {
|
||||
const map: {[outlet: string]: TreeNode<T>} = {};
|
||||
|
||||
if (node) {
|
||||
|
Reference in New Issue
Block a user