@ -76,12 +76,14 @@ Promise
|
||||
testing.TestBed.initTestEnvironment(
|
||||
testingBrowser.BrowserDynamicTestingModule,
|
||||
testingBrowser.platformBrowserDynamicTesting());
|
||||
|
||||
})
|
||||
.then(function() {
|
||||
// Finally, load all spec files.
|
||||
// This will run the tests directly.
|
||||
return Promise.all(
|
||||
allSpecFiles.map(function(moduleName) { return System.import(moduleName); }));
|
||||
return Promise.all(allSpecFiles.map(function(moduleName) {
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
})
|
||||
.then(__karma__.start, function(v) { console.error(v); });
|
||||
.then(__karma__.start, function(v) {
|
||||
console.error(v);
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -8,17 +8,19 @@
|
||||
|
||||
import {NgModuleRef} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Observable, of } from 'rxjs';
|
||||
import {Observable, of} from 'rxjs';
|
||||
|
||||
import {applyRedirects} from '../src/apply_redirects';
|
||||
import {LoadedRouterConfig, Route, Routes} from '../src/config';
|
||||
import {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
||||
import {DefaultUrlSerializer, equalSegments, UrlSegment, UrlSegmentGroup, UrlTree} from '../src/url_tree';
|
||||
|
||||
describe('applyRedirects', () => {
|
||||
const serializer = new DefaultUrlSerializer();
|
||||
let testModule: NgModuleRef<any>;
|
||||
|
||||
beforeEach(() => { testModule = TestBed.inject(NgModuleRef); });
|
||||
beforeEach(() => {
|
||||
testModule = TestBed.inject(NgModuleRef);
|
||||
});
|
||||
|
||||
it('should return the same url tree when no redirects', () => {
|
||||
checkRedirect(
|
||||
@ -31,13 +33,17 @@ describe('applyRedirects', () => {
|
||||
],
|
||||
},
|
||||
],
|
||||
'/a/b', (t: UrlTree) => { expectTreeToBe(t, '/a/b'); });
|
||||
'/a/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/a/b');
|
||||
});
|
||||
});
|
||||
|
||||
it('should add new segments when needed', () => {
|
||||
checkRedirect(
|
||||
[{path: 'a/b', redirectTo: 'a/b/c'}, {path: '**', component: ComponentC}], '/a/b',
|
||||
(t: UrlTree) => { expectTreeToBe(t, '/a/b/c'); });
|
||||
(t: UrlTree) => {
|
||||
expectTreeToBe(t, '/a/b/c');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support redirecting with to an URL with query parameters', () => {
|
||||
@ -57,11 +63,13 @@ describe('applyRedirects', () => {
|
||||
{path: 'a/:aid/b/:bid', redirectTo: 'newa/:aid/newb/:bid'},
|
||||
{path: '**', component: ComponentC}
|
||||
],
|
||||
'/a/1/b/2', (t: UrlTree) => { expectTreeToBe(t, '/newa/1/newb/2'); });
|
||||
'/a/1/b/2', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/newa/1/newb/2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when cannot handle a positional parameter', () => {
|
||||
applyRedirects(testModule.injector, null !, serializer, tree('/a/1'), [
|
||||
applyRedirects(testModule.injector, null!, serializer, tree('/a/1'), [
|
||||
{path: 'a/:id', redirectTo: 'a/:other'}
|
||||
]).subscribe(() => {}, (e) => {
|
||||
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
|
||||
@ -71,7 +79,9 @@ describe('applyRedirects', () => {
|
||||
it('should pass matrix parameters', () => {
|
||||
checkRedirect(
|
||||
[{path: 'a/:id', redirectTo: 'd/a/:id/e'}, {path: '**', component: ComponentC}],
|
||||
'/a;p1=1/1;p2=2', (t: UrlTree) => { expectTreeToBe(t, '/d/a;p1=1/1;p2=2/e'); });
|
||||
'/a;p1=1/1;p2=2', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/d/a;p1=1/1;p2=2/e');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle preserve secondary routes', () => {
|
||||
@ -80,7 +90,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'a/:id', redirectTo: 'd/a/:id/e'},
|
||||
{path: 'c/d', component: ComponentA, outlet: 'aux'}, {path: '**', component: ComponentC}
|
||||
],
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/d/a/1/e(aux:c/d)'); });
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/d/a/1/e(aux:c/d)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect secondary routes', () => {
|
||||
@ -90,7 +102,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'c/d', redirectTo: 'f/c/d/e', outlet: 'aux'},
|
||||
{path: '**', component: ComponentC, outlet: 'aux'}
|
||||
],
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/a/1(aux:f/c/d/e)'); });
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/a/1(aux:f/c/d/e)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the configuration of the route redirected to', () => {
|
||||
@ -105,7 +119,9 @@ describe('applyRedirects', () => {
|
||||
},
|
||||
{path: 'c', redirectTo: 'a'}
|
||||
],
|
||||
'c/b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
|
||||
'c/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/b');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support redirects with both main and aux', () => {
|
||||
@ -119,7 +135,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'b', redirectTo: 'cc', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/(b//aux:b)', (t: UrlTree) => { expectTreeToBe(t, 'a/(bb//aux:cc)'); });
|
||||
'a/(b//aux:b)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(bb//aux:cc)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support redirects with both main and aux (with a nested redirect)', () => {
|
||||
@ -138,7 +156,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'b', redirectTo: 'cc/d', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/(b//aux:b)', (t: UrlTree) => { expectTreeToBe(t, 'a/(bb//aux:cc/dd)'); });
|
||||
'a/(b//aux:b)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(bb//aux:cc/dd)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect wild cards', () => {
|
||||
@ -147,7 +167,9 @@ describe('applyRedirects', () => {
|
||||
{path: '404', component: ComponentA},
|
||||
{path: '**', redirectTo: '/404'},
|
||||
],
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => { expectTreeToBe(t, '/404'); });
|
||||
'/a/1(aux:c/d)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/404');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support absolute redirects', () => {
|
||||
@ -160,7 +182,9 @@ describe('applyRedirects', () => {
|
||||
},
|
||||
{path: '**', component: ComponentC}
|
||||
],
|
||||
'/a/b/1?b=2', (t: UrlTree) => { expectTreeToBe(t, '/absolute/1?a=1&b=2#f1'); });
|
||||
'/a/b/1?b=2', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/absolute/1?a=1&b=2#f1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lazy loading', () => {
|
||||
@ -169,7 +193,7 @@ describe('applyRedirects', () => {
|
||||
const loader = {
|
||||
load: (injector: any, p: any) => {
|
||||
if (injector !== testModule.injector) throw 'Invalid Injector';
|
||||
return of (loadedConfig);
|
||||
return of(loadedConfig);
|
||||
}
|
||||
};
|
||||
const config: Routes = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
@ -188,24 +212,23 @@ describe('applyRedirects', () => {
|
||||
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
|
||||
.subscribe(() => {}, (e) => {
|
||||
expect(e.message).toEqual('Loading Error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should load when all canLoad guards return true', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const guard = () => true;
|
||||
const injector = {
|
||||
get: (token: any) => token === 'guard1' || token === 'guard2' ? guard : {injector}
|
||||
};
|
||||
|
||||
const config = [{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
canLoad: ['guard1', 'guard2'],
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
const config = [
|
||||
{path: 'a', component: ComponentA, canLoad: ['guard1', 'guard2'], loadChildren: 'children'}
|
||||
];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config).forEach(r => {
|
||||
expectTreeToBe(r, '/a/b');
|
||||
@ -214,7 +237,7 @@ describe('applyRedirects', () => {
|
||||
|
||||
it('should not load when any canLoad guards return false', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const trueGuard = () => true;
|
||||
const falseGuard = () => false;
|
||||
@ -231,16 +254,15 @@ describe('applyRedirects', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const config = [{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
canLoad: ['guard1', 'guard2'],
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
const config = [
|
||||
{path: 'a', component: ComponentA, canLoad: ['guard1', 'guard2'], loadChildren: 'children'}
|
||||
];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(
|
||||
() => { throw 'Should not reach'; },
|
||||
() => {
|
||||
throw 'Should not reach';
|
||||
},
|
||||
(e) => {
|
||||
expect(e.message).toEqual(
|
||||
`NavigationCancelingError: Cannot load children because the guard of the route "path: 'a'" returned false`);
|
||||
@ -249,7 +271,7 @@ describe('applyRedirects', () => {
|
||||
|
||||
it('should not load when any canLoad guards is rejected (promises)', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const trueGuard = () => Promise.resolve(true);
|
||||
const falseGuard = () => Promise.reject('someError');
|
||||
@ -266,21 +288,23 @@ describe('applyRedirects', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const config = [{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
canLoad: ['guard1', 'guard2'],
|
||||
loadChildren: 'children'
|
||||
}];
|
||||
const config = [
|
||||
{path: 'a', component: ComponentA, canLoad: ['guard1', 'guard2'], loadChildren: 'children'}
|
||||
];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe(
|
||||
() => { throw 'Should not reach'; }, (e) => { expect(e).toEqual('someError'); });
|
||||
() => {
|
||||
throw 'Should not reach';
|
||||
},
|
||||
(e) => {
|
||||
expect(e).toEqual('someError');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with objects implementing the CanLoad interface', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const guard = {canLoad: () => Promise.resolve(true)};
|
||||
const injector = {get: (token: any) => token === 'guard' ? guard : {injector}};
|
||||
@ -289,13 +313,18 @@ describe('applyRedirects', () => {
|
||||
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
|
||||
.subscribe((r) => { expectTreeToBe(r, '/a/b'); }, (e) => { throw 'Should not reach'; });
|
||||
|
||||
.subscribe(
|
||||
(r) => {
|
||||
expectTreeToBe(r, '/a/b');
|
||||
},
|
||||
(e) => {
|
||||
throw 'Should not reach';
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass UrlSegments to functions implementing the canLoad guard interface', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
let passedUrlSegments: UrlSegment[];
|
||||
|
||||
@ -316,13 +345,14 @@ describe('applyRedirects', () => {
|
||||
expect(passedUrlSegments[0].path).toBe('a');
|
||||
expect(passedUrlSegments[1].path).toBe('b');
|
||||
},
|
||||
(e) => { throw 'Should not reach'; });
|
||||
|
||||
(e) => {
|
||||
throw 'Should not reach';
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass UrlSegments to objects implementing the canLoad guard interface', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
let passedUrlSegments: UrlSegment[];
|
||||
|
||||
@ -345,14 +375,15 @@ describe('applyRedirects', () => {
|
||||
expect(passedUrlSegments[0].path).toBe('a');
|
||||
expect(passedUrlSegments[1].path).toBe('b');
|
||||
},
|
||||
(e) => { throw 'Should not reach'; });
|
||||
|
||||
(e) => {
|
||||
throw 'Should not reach';
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with absolute redirects', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const config: Routes =
|
||||
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
|
||||
@ -371,7 +402,7 @@ describe('applyRedirects', () => {
|
||||
load: (injector: any, p: any) => {
|
||||
if (called) throw new Error('Should not be called twice');
|
||||
called = true;
|
||||
return of (loadedConfig);
|
||||
return of(loadedConfig);
|
||||
}
|
||||
};
|
||||
|
||||
@ -386,42 +417,50 @@ describe('applyRedirects', () => {
|
||||
expectTreeToBe(r, 'a?k2');
|
||||
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
||||
},
|
||||
(e) => { throw 'Should not reach'; });
|
||||
(e) => {
|
||||
throw 'Should not reach';
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the configuration of a wildcard route', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const config: Routes = [{path: '**', loadChildren: 'children'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((config[0] as any)._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => {
|
||||
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the configuration after a local redirect from a wildcard route', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const config: Routes =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((config[0] as any)._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => {
|
||||
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the configuration after an absolute redirect from a wildcard route', () => {
|
||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||
|
||||
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||
const loader = {load: (injector: any, p: any) => of(loadedConfig)};
|
||||
|
||||
const config: Routes =
|
||||
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
|
||||
|
||||
applyRedirects(testModule.injector, <any>loader, serializer, tree('xyz'), config)
|
||||
.forEach(r => { expect((config[0] as any)._loadedConfig).toBe(loadedConfig); });
|
||||
.forEach(r => {
|
||||
expect((config[0] as any)._loadedConfig).toBe(loadedConfig);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -438,7 +477,9 @@ describe('applyRedirects', () => {
|
||||
},
|
||||
{path: '', redirectTo: 'a'}
|
||||
],
|
||||
'b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
|
||||
'b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/b');
|
||||
});
|
||||
});
|
||||
|
||||
it('redirect from an empty path should work (absolute redirect)', () => {
|
||||
@ -453,7 +494,9 @@ describe('applyRedirects', () => {
|
||||
},
|
||||
{path: '', redirectTo: '/a/b'}
|
||||
],
|
||||
'', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
|
||||
'', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/b');
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect empty path route only when terminal', () => {
|
||||
@ -468,10 +511,14 @@ describe('applyRedirects', () => {
|
||||
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
||||
];
|
||||
|
||||
applyRedirects(testModule.injector, null !, serializer, tree('b'), config)
|
||||
applyRedirects(testModule.injector, null!, serializer, tree('b'), config)
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
|
||||
(_) => {
|
||||
throw 'Should not be reached';
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\'');
|
||||
});
|
||||
});
|
||||
|
||||
it('redirect from an empty path should work (nested case)', () => {
|
||||
@ -484,7 +531,9 @@ describe('applyRedirects', () => {
|
||||
},
|
||||
{path: '', redirectTo: 'a'}
|
||||
],
|
||||
'', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
|
||||
'', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/b');
|
||||
});
|
||||
});
|
||||
|
||||
it('redirect to an empty path should work', () => {
|
||||
@ -493,7 +542,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', component: ComponentA, children: [{path: 'b', component: ComponentB}]},
|
||||
{path: 'a', redirectTo: ''}
|
||||
],
|
||||
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'b'); });
|
||||
'a/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('aux split is in the middle', () => {
|
||||
@ -507,7 +558,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
|
||||
'a/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(b//aux:c)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new url segment (terminal)', () => {
|
||||
@ -520,7 +573,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/b', (t: UrlTree) => { expectTreeToBe(t, 'a/b'); });
|
||||
'a/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/b');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -535,7 +590,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
|
||||
'a', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(b//aux:c)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new child (terminal)', () => {
|
||||
@ -548,7 +605,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
|
||||
'a', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(b//aux:c)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work only only primary outlet', () => {
|
||||
@ -560,7 +619,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'c', component: ComponentC, outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/(aux:c)', (t: UrlTree) => { expectTreeToBe(t, 'a/(b//aux:c)'); });
|
||||
'a/(aux:c)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(b//aux:c)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -580,7 +641,9 @@ describe('applyRedirects', () => {
|
||||
{path: '', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/(d//aux:e)', (t: UrlTree) => { expectTreeToBe(t, 'a/(b/d//aux:c/e)'); });
|
||||
'a/(d//aux:e)', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(b/d//aux:c/e)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not create a new child (terminal)', () => {
|
||||
@ -598,10 +661,14 @@ describe('applyRedirects', () => {
|
||||
]
|
||||
}];
|
||||
|
||||
applyRedirects(testModule.injector, null !, serializer, tree('a/(d//aux:e)'), config)
|
||||
applyRedirects(testModule.injector, null!, serializer, tree('a/(d//aux:e)'), config)
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
|
||||
(_) => {
|
||||
throw 'Should not be reached';
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -610,7 +677,9 @@ describe('applyRedirects', () => {
|
||||
it('should not error when no children matching and no url is left', () => {
|
||||
checkRedirect(
|
||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}],
|
||||
'/a', (t: UrlTree) => { expectTreeToBe(t, 'a'); });
|
||||
'/a', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error when no children matching and no url is left (aux routes)', () => {
|
||||
@ -624,16 +693,22 @@ describe('applyRedirects', () => {
|
||||
{path: 'c', component: ComponentC, outlet: 'aux'},
|
||||
]
|
||||
}],
|
||||
'/a', (t: UrlTree) => { expectTreeToBe(t, 'a/(aux:c)'); });
|
||||
'/a', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/(aux:c)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should error when no children matching and some url is left', () => {
|
||||
applyRedirects(
|
||||
testModule.injector, null !, serializer, tree('/a/c'),
|
||||
testModule.injector, null!, serializer, tree('/a/c'),
|
||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
|
||||
.subscribe(
|
||||
(_) => { throw 'Should not be reached'; },
|
||||
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a/c\''); });
|
||||
(_) => {
|
||||
throw 'Should not be reached';
|
||||
},
|
||||
e => {
|
||||
expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a/c\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -653,7 +728,9 @@ describe('applyRedirects', () => {
|
||||
component: ComponentA,
|
||||
children: [{path: 'b', component: ComponentB}]
|
||||
}] as any,
|
||||
'/a/1/b', (t: UrlTree) => { expectTreeToBe(t, 'a/1/b'); });
|
||||
'/a/1/b', (t: UrlTree) => {
|
||||
expectTreeToBe(t, 'a/1/b');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -665,7 +742,9 @@ describe('applyRedirects', () => {
|
||||
{path: 'b/:id', component: ComponentB},
|
||||
{path: 'c/:id', component: ComponentC, outlet: 'aux'}
|
||||
],
|
||||
'a/1;p=99', (t: UrlTree) => { expectTreeToBe(t, '/b/1;p=99(aux:c/1;p=99)'); });
|
||||
'a/1;p=99', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/b/1;p=99(aux:c/1;p=99)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work when using absolute redirects (wildcard)', () => {
|
||||
@ -674,17 +753,21 @@ describe('applyRedirects', () => {
|
||||
{path: '**', redirectTo: '/b(aux:c)'}, {path: 'b', component: ComponentB},
|
||||
{path: 'c', component: ComponentC, outlet: 'aux'}
|
||||
],
|
||||
'a/1', (t: UrlTree) => { expectTreeToBe(t, '/b(aux:c)'); });
|
||||
'a/1', (t: UrlTree) => {
|
||||
expectTreeToBe(t, '/b(aux:c)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when using non-absolute redirects', () => {
|
||||
applyRedirects(
|
||||
testModule.injector, null !, serializer, tree('a'),
|
||||
testModule.injector, null!, serializer, tree('a'),
|
||||
[
|
||||
{path: 'a', redirectTo: 'b(aux:c)'},
|
||||
])
|
||||
.subscribe(
|
||||
() => { throw new Error('should not be reached'); },
|
||||
() => {
|
||||
throw new Error('should not be reached');
|
||||
},
|
||||
(e) => {
|
||||
expect(e.message).toEqual(
|
||||
'Only absolute redirects can have named outlets. redirectTo: \'b(aux:c)\'');
|
||||
@ -694,8 +777,10 @@ describe('applyRedirects', () => {
|
||||
});
|
||||
|
||||
function checkRedirect(config: Routes, url: string, callback: any): void {
|
||||
applyRedirects(TestBed, null !, new DefaultUrlSerializer(), tree(url), config)
|
||||
.subscribe(callback, e => { throw e; });
|
||||
applyRedirects(TestBed, null!, new DefaultUrlSerializer(), tree(url), config)
|
||||
.subscribe(callback, e => {
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
function tree(url: string): UrlTree {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {APP_BASE_HREF, DOCUMENT, Location, ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {ApplicationRef, CUSTOM_ELEMENTS_SCHEMA, Component, NgModule, destroyPlatform} from '@angular/core';
|
||||
import {ApplicationRef, Component, CUSTOM_ELEMENTS_SCHEMA, destroyPlatform, NgModule} from '@angular/core';
|
||||
import {inject} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@ -17,11 +17,13 @@ import {filter, first} from 'rxjs/operators';
|
||||
describe('bootstrap', () => {
|
||||
if (isNode) return;
|
||||
let log: any[] = [];
|
||||
let testProviders: any[] = null !;
|
||||
let testProviders: any[] = null!;
|
||||
|
||||
@Component({selector: 'test-app', template: 'root <router-outlet></router-outlet>'})
|
||||
class RootCmp {
|
||||
constructor() { log.push('RootCmp'); }
|
||||
constructor() {
|
||||
log.push('RootCmp');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'test-app2', template: 'root <router-outlet></router-outlet>'})
|
||||
@ -63,9 +65,10 @@ describe('bootstrap', () => {
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule, RouterModule.forRoot(
|
||||
[{path: '**', component: TestCmpEnabled, resolve: {test: TestResolver}}],
|
||||
{useHash: true, initialNavigation: 'enabled'})
|
||||
BrowserModule,
|
||||
RouterModule.forRoot(
|
||||
[{path: '**', component: TestCmpEnabled, resolve: {test: TestResolver}}],
|
||||
{useHash: true, initialNavigation: 'enabled'})
|
||||
],
|
||||
declarations: [RootCmp, TestCmpEnabled],
|
||||
bootstrap: [RootCmp],
|
||||
@ -81,7 +84,7 @@ describe('bootstrap', () => {
|
||||
|
||||
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
|
||||
const router = res.injector.get(Router);
|
||||
const data = router.routerState.snapshot.root.firstChild !.data;
|
||||
const data = router.routerState.snapshot.root.firstChild!.data;
|
||||
expect(data['test']).toEqual('test-data');
|
||||
expect(log).toEqual([
|
||||
'TestModule', 'NavigationStart', 'RoutesRecognized', 'GuardsCheckStart',
|
||||
@ -141,9 +144,10 @@ describe('bootstrap', () => {
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule, RouterModule.forRoot(
|
||||
[{path: '**', component: TestCmpDiabled, resolve: {test: TestResolver}}],
|
||||
{useHash: true, initialNavigation: 'disabled'})
|
||||
BrowserModule,
|
||||
RouterModule.forRoot(
|
||||
[{path: '**', component: TestCmpDiabled, resolve: {test: TestResolver}}],
|
||||
{useHash: true, initialNavigation: 'disabled'})
|
||||
],
|
||||
declarations: [RootCmp, TestCmpDiabled],
|
||||
bootstrap: [RootCmp],
|
||||
@ -250,7 +254,7 @@ describe('bootstrap', () => {
|
||||
});
|
||||
|
||||
|
||||
it('should restore the scrolling position', async(done) => {
|
||||
it('should restore the scrolling position', async (done) => {
|
||||
@Component({
|
||||
selector: 'component-a',
|
||||
template: `
|
||||
|
@ -32,12 +32,12 @@ describe('config', () => {
|
||||
it('should throw for undefined route in children', () => {
|
||||
expect(() => {
|
||||
validateConfig([{
|
||||
path: 'a',
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
,
|
||||
]
|
||||
}] as any);
|
||||
path: 'a',
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
,
|
||||
]
|
||||
}] as any);
|
||||
}).toThrowError(/Invalid configuration of route 'a'/);
|
||||
});
|
||||
|
||||
@ -66,46 +66,58 @@ describe('config', () => {
|
||||
});
|
||||
|
||||
it('should properly report deeply nested path', () => {
|
||||
expect(() => validateConfig([{
|
||||
path: 'a',
|
||||
children: [{path: 'b', children: [{path: 'c', children: [{path: 'd'}]}]}]
|
||||
}]))
|
||||
expect(
|
||||
() => validateConfig([
|
||||
{path: 'a', children: [{path: 'b', children: [{path: 'c', children: [{path: 'd'}]}]}]}
|
||||
]))
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a/b/c/d'. One of the following must be provided: component, redirectTo, children or loadChildren`);
|
||||
});
|
||||
|
||||
it('should throw when redirectTo and loadChildren are used together', () => {
|
||||
expect(() => { validateConfig([{path: 'a', redirectTo: 'b', loadChildren: 'value'}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', redirectTo: 'b', loadChildren: 'value'}]);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a': redirectTo and loadChildren cannot be used together`);
|
||||
});
|
||||
|
||||
it('should throw when children and loadChildren are used together', () => {
|
||||
expect(() => { validateConfig([{path: 'a', children: [], loadChildren: 'value'}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', children: [], loadChildren: 'value'}]);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a': children and loadChildren cannot be used together`);
|
||||
});
|
||||
|
||||
it('should throw when component and redirectTo are used together', () => {
|
||||
expect(() => { validateConfig([{path: 'a', component: ComponentA, redirectTo: 'b'}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', component: ComponentA, redirectTo: 'b'}]);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a': redirectTo and component cannot be used together`);
|
||||
});
|
||||
|
||||
it('should throw when path and matcher are used together', () => {
|
||||
expect(() => { validateConfig([{path: 'a', matcher: <any>'someFunc', children: []}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', matcher: <any>'someFunc', children: []}]);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a': path and matcher cannot be used together`);
|
||||
});
|
||||
|
||||
it('should throw when path and matcher are missing', () => {
|
||||
expect(() => { validateConfig([{component: null, redirectTo: 'b'}] as any); })
|
||||
expect(() => {
|
||||
validateConfig([{component: null, redirectTo: 'b'}] as any);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route '': routes must have either a path or a matcher specified`);
|
||||
});
|
||||
|
||||
it('should throw when none of component and children or direct are missing', () => {
|
||||
expect(() => { validateConfig([{path: 'a'}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a'}]);
|
||||
})
|
||||
.toThrowError(
|
||||
`Invalid configuration of route 'a'. One of the following must be provided: component, redirectTo, children or loadChildren`);
|
||||
});
|
||||
@ -124,13 +136,17 @@ describe('config', () => {
|
||||
});
|
||||
|
||||
it('should throw when pathMatch is invalid', () => {
|
||||
expect(() => { validateConfig([{path: 'a', pathMatch: 'invalid', component: ComponentB}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', pathMatch: 'invalid', component: ComponentB}]);
|
||||
})
|
||||
.toThrowError(
|
||||
/Invalid configuration of route 'a': pathMatch can only be set to 'prefix' or 'full'/);
|
||||
});
|
||||
|
||||
it('should throw when path/outlet combination is invalid', () => {
|
||||
expect(() => { validateConfig([{path: 'a', outlet: 'aux'}]); })
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', outlet: 'aux'}]);
|
||||
})
|
||||
.toThrowError(
|
||||
/Invalid configuration of route 'a': a componentless route without children or loadChildren cannot have a named outlet set/);
|
||||
expect(() => validateConfig([{path: 'a', outlet: '', children: []}])).not.toThrow();
|
||||
@ -139,7 +155,9 @@ describe('config', () => {
|
||||
});
|
||||
|
||||
it('should not throw when path/outlet combination is valid', () => {
|
||||
expect(() => { validateConfig([{path: 'a', outlet: 'aux', children: []}]); }).not.toThrow();
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', outlet: 'aux', children: []}]);
|
||||
}).not.toThrow();
|
||||
expect(() => {
|
||||
validateConfig([{path: 'a', outlet: 'aux', loadChildren: 'child'}]);
|
||||
}).not.toThrow();
|
||||
|
@ -10,7 +10,7 @@ import {Routes} from '../src/config';
|
||||
import {createRouterState} from '../src/create_router_state';
|
||||
import {recognize} from '../src/recognize';
|
||||
import {DefaultRouteReuseStrategy} from '../src/route_reuse_strategy';
|
||||
import {ActivatedRoute, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from '../src/router_state';
|
||||
import {ActivatedRoute, advanceActivatedRoute, createEmptyState, RouterState, RouterStateSnapshot} from '../src/router_state';
|
||||
import {PRIMARY_OUTLET} from '../src/shared';
|
||||
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree';
|
||||
import {TreeNode} from '../src/utils/tree';
|
||||
@ -18,18 +18,19 @@ import {TreeNode} from '../src/utils/tree';
|
||||
describe('create router state', () => {
|
||||
const reuseStrategy = new DefaultRouteReuseStrategy();
|
||||
|
||||
const emptyState = () => createEmptyState(
|
||||
new (UrlTree as any)(new UrlSegmentGroup([], {}), {}, null !), RootComponent);
|
||||
const emptyState = () =>
|
||||
createEmptyState(new (UrlTree as any)(new UrlSegmentGroup([], {}), {}, null!), RootComponent);
|
||||
|
||||
it('should create new state', () => {
|
||||
const state = createRouterState(
|
||||
reuseStrategy, createState(
|
||||
[
|
||||
{path: 'a', component: ComponentA},
|
||||
{path: 'b', component: ComponentB, outlet: 'left'},
|
||||
{path: 'c', component: ComponentC, outlet: 'right'}
|
||||
],
|
||||
'a(left:b//right:c)'),
|
||||
reuseStrategy,
|
||||
createState(
|
||||
[
|
||||
{path: 'a', component: ComponentA},
|
||||
{path: 'b', component: ComponentB, outlet: 'left'},
|
||||
{path: 'c', component: ComponentC, outlet: 'right'}
|
||||
],
|
||||
'a(left:b//right:c)'),
|
||||
emptyState());
|
||||
|
||||
checkActivatedRoute(state.root, RootComponent);
|
||||
@ -63,9 +64,8 @@ describe('create router state', () => {
|
||||
it('should handle componentless routes', () => {
|
||||
const config = [{
|
||||
path: 'a/:id',
|
||||
children: [
|
||||
{path: 'b', component: ComponentA}, {path: 'c', component: ComponentB, outlet: 'right'}
|
||||
]
|
||||
children:
|
||||
[{path: 'b', component: ComponentA}, {path: 'c', component: ComponentB, outlet: 'right'}]
|
||||
}];
|
||||
|
||||
|
||||
@ -76,8 +76,8 @@ describe('create router state', () => {
|
||||
createRouterState(reuseStrategy, createState(config, 'a/2;p=22/(b//right:c)'), prevState);
|
||||
|
||||
expect(prevState.root).toBe(state.root);
|
||||
const prevP = (prevState as any).firstChild(prevState.root) !;
|
||||
const currP = (state as any).firstChild(state.root) !;
|
||||
const prevP = (prevState as any).firstChild(prevState.root)!;
|
||||
const currP = (state as any).firstChild(state.root)!;
|
||||
expect(prevP).toBe(currP);
|
||||
|
||||
const currC = (state as any).children(currP);
|
||||
@ -121,7 +121,7 @@ function advanceNode(node: TreeNode<ActivatedRoute>): void {
|
||||
}
|
||||
|
||||
function createState(config: Routes, url: string): RouterStateSnapshot {
|
||||
let res: RouterStateSnapshot = undefined !;
|
||||
let res: RouterStateSnapshot = undefined!;
|
||||
recognize(RootComponent, config, tree(url), url).forEach(s => res = s);
|
||||
return res;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
import {createUrlTree} from '../src/create_url_tree';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, advanceActivatedRoute} from '../src/router_state';
|
||||
import {PRIMARY_OUTLET, Params} from '../src/shared';
|
||||
import {Params, PRIMARY_OUTLET} from '../src/shared';
|
||||
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree';
|
||||
|
||||
describe('createUrlTree', () => {
|
||||
@ -247,10 +247,10 @@ function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragme
|
||||
[], <any>{}, <any>{}, '', <any>{}, PRIMARY_OUTLET, 'someComponent', null, tree.root, -1,
|
||||
<any>null);
|
||||
const a = new (ActivatedRoute as any)(
|
||||
new BehaviorSubject(null !), new BehaviorSubject(null !), new BehaviorSubject(null !),
|
||||
new BehaviorSubject(null !), new BehaviorSubject(null !), PRIMARY_OUTLET, 'someComponent', s);
|
||||
new BehaviorSubject(null!), new BehaviorSubject(null!), new BehaviorSubject(null!),
|
||||
new BehaviorSubject(null!), new BehaviorSubject(null!), PRIMARY_OUTLET, 'someComponent', s);
|
||||
advanceActivatedRoute(a);
|
||||
return createUrlTree(a, tree, commands, queryParams !, fragment !);
|
||||
return createUrlTree(a, tree, commands, queryParams!, fragment!);
|
||||
}
|
||||
|
||||
function create(
|
||||
@ -263,8 +263,8 @@ function create(
|
||||
[], <any>{}, <any>{}, '', <any>{}, PRIMARY_OUTLET, 'someComponent', null, <any>segment,
|
||||
startIndex, <any>null);
|
||||
const a = new (ActivatedRoute as any)(
|
||||
new BehaviorSubject(null !), new BehaviorSubject(null !), new BehaviorSubject(null !),
|
||||
new BehaviorSubject(null !), new BehaviorSubject(null !), PRIMARY_OUTLET, 'someComponent', s);
|
||||
new BehaviorSubject(null!), new BehaviorSubject(null!), new BehaviorSubject(null!),
|
||||
new BehaviorSubject(null!), new BehaviorSubject(null!), PRIMARY_OUTLET, 'someComponent', s);
|
||||
advanceActivatedRoute(a);
|
||||
return createUrlTree(a, tree, commands, queryParams !, fragment !);
|
||||
return createUrlTree(a, tree, commands, queryParams!, fragment!);
|
||||
}
|
||||
|
@ -10,13 +10,17 @@ import {Type} from '@angular/core';
|
||||
|
||||
import {Data, ResolveData, Route} from '../src/config';
|
||||
import {ActivatedRouteSnapshot} from '../src/router_state';
|
||||
import {PRIMARY_OUTLET, ParamMap, Params, convertToParamMap} from '../src/shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
||||
import {convertToParamMap, ParamMap, Params, PRIMARY_OUTLET} from '../src/shared';
|
||||
import {equalSegments, UrlSegment, UrlSegmentGroup, UrlTree} from '../src/url_tree';
|
||||
|
||||
export class Logger {
|
||||
logs: string[] = [];
|
||||
add(thing: string) { this.logs.push(thing); }
|
||||
empty() { this.logs.length = 0; }
|
||||
add(thing: string) {
|
||||
this.logs.push(thing);
|
||||
}
|
||||
empty() {
|
||||
this.logs.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function provideTokenLogger(token: string, returnValue = true as boolean | UrlTree) {
|
||||
@ -33,8 +37,7 @@ export declare type ARSArgs = {
|
||||
queryParams?: Params,
|
||||
fragment?: string,
|
||||
data?: Data,
|
||||
outlet?: string,
|
||||
component: Type<any>| string | null,
|
||||
outlet?: string, component: Type<any>| string | null,
|
||||
routeConfig?: Route | null,
|
||||
urlSegment?: UrlSegmentGroup,
|
||||
lastPathIndex?: number,
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {Observable, Observer, of } from 'rxjs';
|
||||
import {Observable, Observer, of} from 'rxjs';
|
||||
import {every, mergeMap} from 'rxjs/operators';
|
||||
import {TestScheduler} from 'rxjs/testing';
|
||||
|
||||
@ -23,13 +23,18 @@ describe('prioritizedGuardValue operator', () => {
|
||||
let router: Router;
|
||||
const TF = {T: true, F: false};
|
||||
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
||||
beforeEach(() => { testScheduler = new TestScheduler(assertDeepEquals); });
|
||||
beforeEach(() => { router = TestBed.inject(Router); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [RouterTestingModule]});
|
||||
});
|
||||
beforeEach(() => {
|
||||
testScheduler = new TestScheduler(assertDeepEquals);
|
||||
});
|
||||
beforeEach(() => {
|
||||
router = TestBed.inject(Router);
|
||||
});
|
||||
|
||||
it('should return true if all values are true', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const a = cold(' --(T|)', TF);
|
||||
const b = cold(' ----------(T|)', TF);
|
||||
const c = cold(' ------(T|)', TF);
|
||||
@ -38,13 +43,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------T--';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, TF, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
TF,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false if observables to the left of false have produced a value', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const a = cold(' --(T|)', TF);
|
||||
const b = cold(' ----------(T|)', TF);
|
||||
const c = cold(' ------(F|)', TF);
|
||||
@ -53,13 +60,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------F--';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, TF, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
TF,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore results for unresolved sets of Observables', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const a = cold(' --(T|)', TF);
|
||||
const b = cold(' -------------(T|)', TF);
|
||||
const c = cold(' ------(F|)', TF);
|
||||
@ -71,13 +80,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' ------------T---';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, TF, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
TF,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return UrlTree if higher priority guards have resolved', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const urlTree = router.parseUrl('/');
|
||||
|
||||
const urlLookup = {U: urlTree};
|
||||
@ -91,13 +102,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------U---';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, urlLookup, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
urlLookup,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false even with UrlTree if UrlTree is lower priority', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const urlTree = router.parseUrl('/');
|
||||
|
||||
const urlLookup = {U: urlTree};
|
||||
@ -111,13 +124,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------F---';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, TF, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
TF,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return UrlTree even after a false if the false is lower priority', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const urlTree = router.parseUrl('/');
|
||||
|
||||
const urlLookup = {U: urlTree};
|
||||
@ -131,13 +146,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------U----';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, urlLookup, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
urlLookup,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the highest priority UrlTree', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const urlTreeU = router.parseUrl('/u');
|
||||
const urlTreeR = router.parseUrl('/r');
|
||||
const urlTreeL = router.parseUrl('/l');
|
||||
@ -153,13 +170,15 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' -------------U---';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, urlLookup, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
urlLookup,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
it('should propagate errors', () => {
|
||||
testScheduler.run(({hot, cold, expectObservable}) => {
|
||||
|
||||
const a = cold(' --(T|)', TF);
|
||||
const b = cold(' ------#', TF);
|
||||
const c = cold(' ----------(F|)', TF);
|
||||
@ -168,11 +187,12 @@ describe('prioritizedGuardValue operator', () => {
|
||||
const expected = ' ---------#';
|
||||
|
||||
expectObservable(source.pipe(prioritizedGuardValue()))
|
||||
.toBe(expected, TF, /* an error here maybe */);
|
||||
.toBe(
|
||||
expected,
|
||||
TF,
|
||||
/* an error here maybe */);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -9,21 +9,21 @@
|
||||
import {Routes} from '../src/config';
|
||||
import {recognize} from '../src/recognize';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../src/router_state';
|
||||
import {PRIMARY_OUTLET, Params} from '../src/shared';
|
||||
import {Params, PRIMARY_OUTLET} from '../src/shared';
|
||||
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
|
||||
|
||||
describe('recognize', () => {
|
||||
it('should work', () => {
|
||||
checkRecognize([{path: 'a', component: ComponentA}], 'a', (s: any) => {
|
||||
checkActivatedRoute(s.root, '', {}, RootComponent);
|
||||
checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute(s.firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
||||
it('should freeze params object', () => {
|
||||
checkRecognize([{path: 'a/:id', component: ComponentA}], 'a/10', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute(s.root, '', {}, RootComponent);
|
||||
const child = (s as any).firstChild(s.root) !;
|
||||
const child = (s as any).firstChild(s.root)!;
|
||||
expect(Object.isFrozen(child.params)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -79,11 +79,11 @@ describe('recognize', () => {
|
||||
expect((s.root as any)._urlSegment).toBe(url.root);
|
||||
expect((s.root as any)._lastPathIndex).toBe(-1);
|
||||
|
||||
const compA = (s as any).firstChild(s.root) !;
|
||||
const compA = (s as any).firstChild(s.root)!;
|
||||
expect(compA._urlSegment).toBe((url.root as any).children[PRIMARY_OUTLET]);
|
||||
expect(compA._lastPathIndex).toBe(1);
|
||||
|
||||
const compC = (s as any).firstChild(<any>compA) !;
|
||||
const compC = (s as any).firstChild(<any>compA)!;
|
||||
expect(compC._urlSegment).toBe((url.root as any).children[PRIMARY_OUTLET]);
|
||||
expect(compC._lastPathIndex).toBe(2);
|
||||
});
|
||||
@ -101,11 +101,11 @@ describe('recognize', () => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const compA = (s as any).firstChild(s.root) !;
|
||||
const compA = (s as any).firstChild(s.root)!;
|
||||
expect(compA._urlSegment).toBe((url as any).root.children[PRIMARY_OUTLET]);
|
||||
expect(compA._lastPathIndex).toBe(0);
|
||||
|
||||
const compC = (s as any).firstChild(<any>compA) !;
|
||||
const compC = (s as any).firstChild(<any>compA)!;
|
||||
expect(compC._urlSegment).toBe((url as any).root.children[PRIMARY_OUTLET]);
|
||||
expect(compC._lastPathIndex).toBe(2);
|
||||
});
|
||||
@ -119,9 +119,9 @@ describe('recognize', () => {
|
||||
],
|
||||
'a/paramA', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute(s.root, '', {}, RootComponent);
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root)) !, 'paramA', {id: 'paramA'},
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root))!, 'paramA', {id: 'paramA'},
|
||||
ComponentB);
|
||||
});
|
||||
|
||||
@ -130,7 +130,7 @@ describe('recognize', () => {
|
||||
(s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute(s.root, '', {}, RootComponent);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild(s.root) !, 'a/paramA', {id: 'paramA'}, ComponentC);
|
||||
(s as any).firstChild(s.root)!, 'a/paramA', {id: 'paramA'}, ComponentC);
|
||||
});
|
||||
});
|
||||
|
||||
@ -154,8 +154,7 @@ describe('recognize', () => {
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
{path: 'c', component: ComponentC, outlet: 'left'}
|
||||
{path: 'b', component: ComponentB}, {path: 'c', component: ComponentC, outlet: 'left'}
|
||||
]
|
||||
},
|
||||
],
|
||||
@ -190,7 +189,7 @@ describe('recognize', () => {
|
||||
const c = (s as any).children(s.root);
|
||||
checkActivatedRoute(c[0], 'a', {a1: '11', a2: '22'}, ComponentA);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild(<any>c[0]) !, 'b', {b1: '111', b2: '222'}, ComponentB);
|
||||
(s as any).firstChild(<any>c[0])!, 'b', {b1: '111', b2: '222'}, ComponentB);
|
||||
checkActivatedRoute(c[1], 'c', {c1: '1111', c2: '2222'}, ComponentC, 'left');
|
||||
});
|
||||
});
|
||||
@ -199,7 +198,7 @@ describe('recognize', () => {
|
||||
it('should set static data', () => {
|
||||
checkRecognize(
|
||||
[{path: 'a', data: {one: 1}, component: ComponentA}], 'a', (s: RouterStateSnapshot) => {
|
||||
const r: ActivatedRouteSnapshot = (s as any).firstChild(s.root) !;
|
||||
const r: ActivatedRouteSnapshot = (s as any).firstChild(s.root)!;
|
||||
expect(r.data).toEqual({one: 1});
|
||||
});
|
||||
});
|
||||
@ -213,7 +212,7 @@ describe('recognize', () => {
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
const r: ActivatedRouteSnapshot =
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root)) !;
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root))!;
|
||||
expect(r.data).toEqual({one: 1, two: 2});
|
||||
});
|
||||
});
|
||||
@ -227,7 +226,7 @@ describe('recognize', () => {
|
||||
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
|
||||
}],
|
||||
'a/b', (s: any /* RouterStateSnapshot */) => {
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root))!;
|
||||
expect(r.data).toEqual({two: 2});
|
||||
});
|
||||
});
|
||||
@ -241,7 +240,7 @@ describe('recognize', () => {
|
||||
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
|
||||
}],
|
||||
'a/b', (s: any /* RouterStateSnapshot */) => {
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root))!;
|
||||
expect(r.data).toEqual({one: 1, two: 2});
|
||||
}, 'always');
|
||||
});
|
||||
@ -249,7 +248,7 @@ describe('recognize', () => {
|
||||
it('should set resolved data', () => {
|
||||
checkRecognize(
|
||||
[{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a', (s: any) => {
|
||||
const r: any = s.firstChild(s.root) !;
|
||||
const r: any = s.firstChild(s.root)!;
|
||||
expect(r._resolve).toEqual({one: 'some-token'});
|
||||
});
|
||||
});
|
||||
@ -259,7 +258,7 @@ describe('recognize', () => {
|
||||
describe('root', () => {
|
||||
it('should work', () => {
|
||||
checkRecognize([{path: '', component: ComponentA}], '', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, '', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, '', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
||||
@ -267,7 +266,7 @@ describe('recognize', () => {
|
||||
checkRecognize(
|
||||
[{path: '', pathMatch: 'full', component: ComponentA}], '',
|
||||
(s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, '', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, '', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
||||
@ -275,9 +274,9 @@ describe('recognize', () => {
|
||||
checkRecognize(
|
||||
[{path: '', component: ComponentA, children: [{path: '', component: ComponentB}]}], '',
|
||||
(s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, '', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, '', {}, ComponentA);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root)) !, '', {}, ComponentB);
|
||||
(s as any).firstChild(<any>(s as any).firstChild(s.root))!, '', {}, ComponentB);
|
||||
});
|
||||
});
|
||||
|
||||
@ -291,11 +290,11 @@ describe('recognize', () => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const c = s.firstChild(s.root) !;
|
||||
const c = s.firstChild(s.root)!;
|
||||
expect(c._urlSegment).toBe(url.root);
|
||||
expect(c._lastPathIndex).toBe(-1);
|
||||
|
||||
const c2 = s.firstChild(<any>s.firstChild(s.root)) !;
|
||||
const c2 = s.firstChild(<any>s.firstChild(s.root))!;
|
||||
expect(c2._urlSegment).toBe(url.root);
|
||||
expect(c2._lastPathIndex).toBe(-1);
|
||||
});
|
||||
@ -306,17 +305,15 @@ describe('recognize', () => {
|
||||
[{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [
|
||||
{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}
|
||||
]
|
||||
children:
|
||||
[{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}]
|
||||
}],
|
||||
'/a;p=1', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {p: '1'}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {p: '1'}, ComponentA);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild((s as any).firstChild(s.root) !) !, '', {p: '1'},
|
||||
ComponentB);
|
||||
(s as any).firstChild((s as any).firstChild(s.root)!)!, '', {p: '1'}, ComponentB);
|
||||
checkActivatedRoute(
|
||||
(s as any).firstChild((s as any).firstChild((s as any).firstChild(s.root) !) !) !,
|
||||
(s as any).firstChild((s as any).firstChild((s as any).firstChild(s.root)!)!)!,
|
||||
'', {p: '1'}, ComponentC);
|
||||
});
|
||||
});
|
||||
@ -329,14 +326,13 @@ describe('recognize', () => {
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
{path: '', component: ComponentC, outlet: 'aux'}
|
||||
{path: 'b', component: ComponentB}, {path: '', component: ComponentC, outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(c[0], 'b', {}, ComponentB);
|
||||
checkActivatedRoute(c[1], '', {}, ComponentC, 'aux');
|
||||
});
|
||||
@ -365,13 +361,13 @@ describe('recognize', () => {
|
||||
|
||||
checkRecognize(config, 'parent/b', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute(s.root, '', {}, RootComponent);
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'parent', {}, undefined !);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'parent', {}, undefined!);
|
||||
|
||||
const cc = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const cc = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(cc[0], '', {}, ComponentA);
|
||||
checkActivatedRoute(cc[1], '', {}, ComponentD, 'secondary');
|
||||
|
||||
checkActivatedRoute((s as any).firstChild(cc[0]) !, 'b', {}, ComponentB);
|
||||
checkActivatedRoute((s as any).firstChild(cc[0])!, 'b', {}, ComponentB);
|
||||
});
|
||||
});
|
||||
|
||||
@ -386,9 +382,9 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
expect(c.length).toEqual(1);
|
||||
checkActivatedRoute(c[0], 'b', {}, ComponentB);
|
||||
});
|
||||
@ -401,8 +397,7 @@ describe('recognize', () => {
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
{path: '', component: ComponentC, outlet: 'aux'}
|
||||
{path: 'b', component: ComponentB}, {path: '', component: ComponentC, outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
url, 'a/b')
|
||||
@ -410,11 +405,11 @@ describe('recognize', () => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const a = s.firstChild(s.root) !;
|
||||
const a = s.firstChild(s.root)!;
|
||||
expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(a._lastPathIndex).toBe(0);
|
||||
|
||||
const b = s.firstChild(a) !;
|
||||
const b = s.firstChild(a)!;
|
||||
expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(b._lastPathIndex).toBe(1);
|
||||
|
||||
@ -429,24 +424,23 @@ describe('recognize', () => {
|
||||
recognize(
|
||||
RootComponent, [{
|
||||
path: 'a',
|
||||
children: [
|
||||
{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}
|
||||
]
|
||||
children:
|
||||
[{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}]
|
||||
}],
|
||||
url, 'a')
|
||||
.forEach((s: any) => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const a = s.firstChild(s.root) !;
|
||||
const a = s.firstChild(s.root)!;
|
||||
expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(a._lastPathIndex).toBe(0);
|
||||
|
||||
const b = s.firstChild(a) !;
|
||||
const b = s.firstChild(a)!;
|
||||
expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(b._lastPathIndex).toBe(0);
|
||||
|
||||
const c = s.firstChild(b) !;
|
||||
const c = s.firstChild(b)!;
|
||||
expect(c._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(c._lastPathIndex).toBe(0);
|
||||
});
|
||||
@ -473,19 +467,19 @@ describe('recognize', () => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const a = s.firstChild(s.root) !;
|
||||
const a = s.firstChild(s.root)!;
|
||||
expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(a._lastPathIndex).toBe(0);
|
||||
|
||||
const b = s.firstChild(a) !;
|
||||
const b = s.firstChild(a)!;
|
||||
expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(b._lastPathIndex).toBe(1);
|
||||
|
||||
const c = s.firstChild(b) !;
|
||||
const c = s.firstChild(b)!;
|
||||
expect(c._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(c._lastPathIndex).toBe(1);
|
||||
|
||||
const d = s.firstChild(c) !;
|
||||
const d = s.firstChild(c)!;
|
||||
expect(d._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]);
|
||||
expect(d._lastPathIndex).toBe(1);
|
||||
});
|
||||
@ -496,24 +490,23 @@ describe('recognize', () => {
|
||||
recognize(
|
||||
RootComponent, [{
|
||||
path: '',
|
||||
children: [
|
||||
{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}
|
||||
]
|
||||
children:
|
||||
[{path: '', component: ComponentB, children: [{path: '', component: ComponentC}]}]
|
||||
}],
|
||||
url, '')
|
||||
.forEach((s: any) => {
|
||||
expect(s.root._urlSegment).toBe(url.root);
|
||||
expect(s.root._lastPathIndex).toBe(-1);
|
||||
|
||||
const a = (s as any).firstChild(s.root) !;
|
||||
const a = (s as any).firstChild(s.root)!;
|
||||
expect(a._urlSegment).toBe(url.root);
|
||||
expect(a._lastPathIndex).toBe(-1);
|
||||
|
||||
const b = (s as any).firstChild(a) !;
|
||||
const b = (s as any).firstChild(a)!;
|
||||
expect(b._urlSegment).toBe(url.root);
|
||||
expect(b._lastPathIndex).toBe(-1);
|
||||
|
||||
const c = (s as any).firstChild(b) !;
|
||||
const c = (s as any).firstChild(b)!;
|
||||
expect(c._urlSegment).toBe(url.root);
|
||||
expect(c._lastPathIndex).toBe(-1);
|
||||
});
|
||||
@ -532,9 +525,9 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'a', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(c[0], '', {}, ComponentB);
|
||||
checkActivatedRoute(c[1], '', {}, ComponentC, 'aux');
|
||||
});
|
||||
@ -551,9 +544,9 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'a', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(c[0], '', {}, ComponentB);
|
||||
checkActivatedRoute(c[1], '', {}, ComponentC, 'aux');
|
||||
});
|
||||
@ -570,9 +563,9 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'a/(aux:c)', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(c[0], '', {}, ComponentB);
|
||||
checkActivatedRoute(c[1], 'c', {}, ComponentC, 'aux');
|
||||
});
|
||||
@ -612,13 +605,13 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'a/(d//aux:e)', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a', {}, ComponentA);
|
||||
|
||||
const c = (s as any).children((s as any).firstChild(s.root) !);
|
||||
const c = (s as any).children((s as any).firstChild(s.root)!);
|
||||
checkActivatedRoute(c[0], '', {}, ComponentB);
|
||||
checkActivatedRoute((s as any).firstChild(c[0]) !, 'd', {}, ComponentD);
|
||||
checkActivatedRoute((s as any).firstChild(c[0])!, 'd', {}, ComponentD);
|
||||
checkActivatedRoute(c[1], '', {}, ComponentC, 'aux');
|
||||
checkActivatedRoute((s as any).firstChild(c[1]) !, 'e', {}, ComponentE);
|
||||
checkActivatedRoute((s as any).firstChild(c[1])!, 'e', {}, ComponentE);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -628,7 +621,7 @@ describe('recognize', () => {
|
||||
it('should support simple wildcards', () => {
|
||||
checkRecognize(
|
||||
[{path: '**', component: ComponentA}], 'a/b/c/d;a1=11', (s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute((s as any).firstChild(s.root) !, 'a/b/c/d', {a1: '11'}, ComponentA);
|
||||
checkActivatedRoute((s as any).firstChild(s.root)!, 'a/b/c/d', {a1: '11'}, ComponentA);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -639,13 +632,12 @@ describe('recognize', () => {
|
||||
[{
|
||||
path: 'p/:id',
|
||||
children: [
|
||||
{path: 'a', component: ComponentA},
|
||||
{path: 'b', component: ComponentB, outlet: 'aux'}
|
||||
{path: 'a', component: ComponentA}, {path: 'b', component: ComponentB, outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'p/11;pp=22/(a;pa=33//aux:b;pb=44)', (s: RouterStateSnapshot) => {
|
||||
const p = (s as any).firstChild(s.root) !;
|
||||
checkActivatedRoute(p, 'p/11', {id: '11', pp: '22'}, undefined !);
|
||||
const p = (s as any).firstChild(s.root)!;
|
||||
checkActivatedRoute(p, 'p/11', {id: '11', pp: '22'}, undefined!);
|
||||
|
||||
const c = (s as any).children(p);
|
||||
checkActivatedRoute(c[0], 'a', {id: '11', pp: '22', pa: '33'}, ComponentA);
|
||||
@ -659,24 +651,22 @@ describe('recognize', () => {
|
||||
path: 'p/:id',
|
||||
children: [{
|
||||
path: 'a/:name',
|
||||
children: [{
|
||||
path: 'b',
|
||||
component: ComponentB,
|
||||
children: [{path: 'c', component: ComponentC}]
|
||||
}]
|
||||
children: [
|
||||
{path: 'b', component: ComponentB, children: [{path: 'c', component: ComponentC}]}
|
||||
]
|
||||
}]
|
||||
}],
|
||||
'p/11/a/victor/b/c', (s: RouterStateSnapshot) => {
|
||||
const p = (s as any).firstChild(s.root) !;
|
||||
checkActivatedRoute(p, 'p/11', {id: '11'}, undefined !);
|
||||
const p = (s as any).firstChild(s.root)!;
|
||||
checkActivatedRoute(p, 'p/11', {id: '11'}, undefined!);
|
||||
|
||||
const a = (s as any).firstChild(p) !;
|
||||
checkActivatedRoute(a, 'a/victor', {id: '11', name: 'victor'}, undefined !);
|
||||
const a = (s as any).firstChild(p)!;
|
||||
checkActivatedRoute(a, 'a/victor', {id: '11', name: 'victor'}, undefined!);
|
||||
|
||||
const b = (s as any).firstChild(a) !;
|
||||
const b = (s as any).firstChild(a)!;
|
||||
checkActivatedRoute(b, 'b', {id: '11', name: 'victor'}, ComponentB);
|
||||
|
||||
const c = (s as any).firstChild(b) !;
|
||||
const c = (s as any).firstChild(b)!;
|
||||
checkActivatedRoute(c, 'c', {}, ComponentC);
|
||||
});
|
||||
});
|
||||
@ -687,15 +677,13 @@ describe('recognize', () => {
|
||||
path: 'p/:id',
|
||||
children: [{
|
||||
path: 'a/:name',
|
||||
children: [{
|
||||
path: 'b',
|
||||
component: ComponentB,
|
||||
children: [{path: 'c', component: ComponentC}]
|
||||
}]
|
||||
children: [
|
||||
{path: 'b', component: ComponentB, children: [{path: 'c', component: ComponentC}]}
|
||||
]
|
||||
}]
|
||||
}],
|
||||
'p/11/a/victor/b/c', (s: any /* RouterStateSnapshot */) => {
|
||||
const c = s.firstChild(s.firstChild(s.firstChild(s.firstChild(s.root) !) !) !) !;
|
||||
const c = s.firstChild(s.firstChild(s.firstChild(s.firstChild(s.root)!)!)!)!;
|
||||
checkActivatedRoute(c, 'c', {id: '11', name: 'victor'}, ComponentC);
|
||||
}, 'always');
|
||||
});
|
||||
@ -707,7 +695,7 @@ describe('recognize', () => {
|
||||
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}],
|
||||
'/a', (s: RouterStateSnapshot) => {
|
||||
const a = (s as any).firstChild(s.root);
|
||||
checkActivatedRoute(a !, 'a', {}, ComponentA);
|
||||
checkActivatedRoute(a!, 'a', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
||||
@ -722,7 +710,7 @@ describe('recognize', () => {
|
||||
]
|
||||
}],
|
||||
'/a', (s: RouterStateSnapshot) => {
|
||||
const a = (s as any).firstChild(s.root) !;
|
||||
const a = (s as any).firstChild(s.root)!;
|
||||
checkActivatedRoute(a, 'a', {}, ComponentA);
|
||||
checkActivatedRoute((a as any).children[0], '', {}, ComponentC, 'aux');
|
||||
});
|
||||
@ -746,9 +734,9 @@ describe('recognize', () => {
|
||||
children: [{path: 'b', component: ComponentB}]
|
||||
}] as any,
|
||||
'/a/1;p=99/b', (s: RouterStateSnapshot) => {
|
||||
const a = (s as any).root.firstChild !;
|
||||
const a = (s as any).root.firstChild!;
|
||||
checkActivatedRoute(a, 'a/1', {id: '1', p: '99'}, ComponentA);
|
||||
checkActivatedRoute((a as any).firstChild !, 'b', {}, ComponentB);
|
||||
checkActivatedRoute((a as any).firstChild!, 'b', {}, ComponentB);
|
||||
});
|
||||
});
|
||||
|
||||
@ -756,7 +744,7 @@ describe('recognize', () => {
|
||||
const matcher = (s: any, g: any, r: any) => s.length === 0 ? ({consumed: s}) : null;
|
||||
|
||||
checkRecognize([{matcher, component: ComponentA}] as any, '', (s: RouterStateSnapshot) => {
|
||||
const a = (s as any).root.firstChild !;
|
||||
const a = (s as any).root.firstChild!;
|
||||
checkActivatedRoute(a, '', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
@ -767,7 +755,7 @@ describe('recognize', () => {
|
||||
checkRecognize(
|
||||
[{path: 'a', component: ComponentA, children: [{matcher, component: ComponentB}]}] as any,
|
||||
'a', (s: RouterStateSnapshot) => {
|
||||
const a = (s as any).root.firstChild !;
|
||||
const a = (s as any).root.firstChild!;
|
||||
checkActivatedRoute(a, 'a', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
@ -800,8 +788,9 @@ describe('recognize', () => {
|
||||
describe('fragment', () => {
|
||||
it('should support fragment', () => {
|
||||
const config = [{path: 'a', component: ComponentA}];
|
||||
checkRecognize(
|
||||
config, 'a#f1', (s: RouterStateSnapshot) => { expect(s.root.fragment).toEqual('f1'); });
|
||||
checkRecognize(config, 'a#f1', (s: RouterStateSnapshot) => {
|
||||
expect(s.root.fragment).toEqual('f1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -825,9 +814,11 @@ describe('recognize', () => {
|
||||
|
||||
function checkRecognize(
|
||||
config: Routes, url: string, callback: any,
|
||||
paramsInheritanceStrategy?: 'emptyOnly' | 'always'): void {
|
||||
paramsInheritanceStrategy?: 'emptyOnly'|'always'): void {
|
||||
recognize(RootComponent, config, tree(url), url, paramsInheritanceStrategy)
|
||||
.subscribe(callback, e => { throw e; });
|
||||
.subscribe(callback, e => {
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
function checkActivatedRoute(
|
||||
|
@ -8,12 +8,11 @@
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ContentChild, NgModule, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('Integration', () => {
|
||||
|
||||
describe('routerLinkActive', () => {
|
||||
it('should not cause infinite loops in the change detection - #15825', fakeAsync(() => {
|
||||
@Component({selector: 'simple', template: 'simple'})
|
||||
@ -73,16 +72,18 @@ describe('Integration', () => {
|
||||
})
|
||||
class ComponentWithRouterLink {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@ViewChild(TemplateRef, {static: true}) templateRef !: TemplateRef<any>;
|
||||
@ViewChild(TemplateRef, {static: true}) templateRef!: TemplateRef<any>;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@ViewChild('container', {read: ViewContainerRef, static: true})
|
||||
container !: ViewContainerRef;
|
||||
container!: ViewContainerRef;
|
||||
|
||||
addLink() {
|
||||
this.container.createEmbeddedView(this.templateRef, {$implicit: '/simple'});
|
||||
}
|
||||
|
||||
removeLink() { this.container.clear(); }
|
||||
removeLink() {
|
||||
this.container.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({template: 'simple'})
|
||||
@ -108,9 +109,7 @@ describe('Integration', () => {
|
||||
|
||||
expect(fixture.nativeElement.innerHTML).toContain('isActive: false');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function advance<T>(fixture: ComponentFixture<T>): void {
|
||||
|
@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import {Location} from '@angular/common';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {of } from 'rxjs';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
import {Routes} from '../src/config';
|
||||
import {ChildActivationStart} from '../src/events';
|
||||
@ -17,19 +17,20 @@ import {checkGuards as checkGuardsOperator} from '../src/operators/check_guards'
|
||||
import {resolveData as resolveDataOperator} from '../src/operators/resolve_data';
|
||||
import {NavigationTransition, Router} from '../src/router';
|
||||
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
||||
import {RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
||||
import {createEmptyStateSnapshot, RouterStateSnapshot} from '../src/router_state';
|
||||
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
|
||||
import {getAllRouteGuards} from '../src/utils/preactivation';
|
||||
import {TreeNode} from '../src/utils/tree';
|
||||
|
||||
import {Logger, createActivatedRouteSnapshot, provideTokenLogger} from './helpers';
|
||||
import {createActivatedRouteSnapshot, Logger, provideTokenLogger} from './helpers';
|
||||
|
||||
describe('Router', () => {
|
||||
|
||||
describe('resetConfig', () => {
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [RouterTestingModule]});
|
||||
});
|
||||
|
||||
it('should copy config to avoid mutations of user-provided objects', () => {
|
||||
const r: Router = TestBed.inject(Router);
|
||||
@ -38,12 +39,12 @@ describe('Router', () => {
|
||||
component: TestComponent,
|
||||
children: [{path: 'b', component: TestComponent}, {path: 'c', component: TestComponent}]
|
||||
}];
|
||||
const children = configs[0].children !;
|
||||
const children = configs[0].children!;
|
||||
|
||||
r.resetConfig(configs);
|
||||
|
||||
const rConfigs = r.config;
|
||||
const rChildren = rConfigs[0].children !;
|
||||
const rChildren = rConfigs[0].children!;
|
||||
|
||||
// routes array and shallow copy
|
||||
expect(configs).not.toBe(rConfigs);
|
||||
@ -63,7 +64,9 @@ describe('Router', () => {
|
||||
describe('resetRootComponentType', () => {
|
||||
class NewRootComponent {}
|
||||
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [RouterTestingModule]});
|
||||
});
|
||||
|
||||
it('should not change root route when updating the root component', () => {
|
||||
const r: Router = TestBed.inject(Router);
|
||||
@ -76,7 +79,9 @@ describe('Router', () => {
|
||||
});
|
||||
|
||||
describe('setUpLocationChangeListener', () => {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [RouterTestingModule]});
|
||||
});
|
||||
|
||||
it('should be idempotent', inject([Router, Location], (r: Router, location: Location) => {
|
||||
r.setUpLocationChangeListener();
|
||||
@ -118,7 +123,6 @@ describe('Router', () => {
|
||||
const CDA_GRANDCHILD_REDIRECT = 'canDeactivate_grandchild_redirect';
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
providers: [
|
||||
@ -137,11 +141,10 @@ describe('Router', () => {
|
||||
CDA_GRANDCHILD_REDIRECT, serializer.parse('/canDeactivate_grandchild_redirect'))
|
||||
]
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
beforeEach(inject([Logger], (_logger: Logger) => {
|
||||
empty = createEmptyStateSnapshot(serializer.parse('/'), null !);
|
||||
empty = createEmptyStateSnapshot(serializer.parse('/'), null!);
|
||||
logger = _logger;
|
||||
events = [];
|
||||
}));
|
||||
@ -164,9 +167,15 @@ describe('Router', () => {
|
||||
guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
|
||||
} as NavigationTransition;
|
||||
|
||||
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
||||
events.push(evt);
|
||||
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
||||
of(testTransition)
|
||||
.pipe(checkGuardsOperator(
|
||||
TestBed,
|
||||
(evt) => {
|
||||
events.push(evt);
|
||||
}))
|
||||
.subscribe((x) => result = !!x.guardsResult, (e) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(events.length).toEqual(2);
|
||||
@ -203,9 +212,15 @@ describe('Router', () => {
|
||||
guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
|
||||
} as NavigationTransition;
|
||||
|
||||
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
||||
events.push(evt);
|
||||
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
||||
of(testTransition)
|
||||
.pipe(checkGuardsOperator(
|
||||
TestBed,
|
||||
(evt) => {
|
||||
events.push(evt);
|
||||
}))
|
||||
.subscribe((x) => result = !!x.guardsResult, (e) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(events.length).toEqual(6);
|
||||
@ -240,9 +255,15 @@ describe('Router', () => {
|
||||
guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
|
||||
} as NavigationTransition;
|
||||
|
||||
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
||||
events.push(evt);
|
||||
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
||||
of(testTransition)
|
||||
.pipe(checkGuardsOperator(
|
||||
TestBed,
|
||||
(evt) => {
|
||||
events.push(evt);
|
||||
}))
|
||||
.subscribe((x) => result = !!x.guardsResult, (e) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(events.length).toEqual(2);
|
||||
@ -290,9 +311,15 @@ describe('Router', () => {
|
||||
guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
|
||||
} as NavigationTransition;
|
||||
|
||||
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
||||
events.push(evt);
|
||||
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
||||
of(testTransition)
|
||||
.pipe(checkGuardsOperator(
|
||||
TestBed,
|
||||
(evt) => {
|
||||
events.push(evt);
|
||||
}))
|
||||
.subscribe((x) => result = !!x.guardsResult, (e) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(events.length).toEqual(4);
|
||||
@ -536,9 +563,9 @@ describe('Router', () => {
|
||||
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||
|
||||
const futureState = new (RouterStateSnapshot as any)(
|
||||
'url', new TreeNode(
|
||||
empty.root,
|
||||
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||
'url', new TreeNode(empty.root, [
|
||||
new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
|
||||
]));
|
||||
|
||||
checkGuards(futureState, empty, TestBed, (result) => {
|
||||
expect(serializer.serialize(result as UrlTree)).toBe('/' + CAC_CHILD_REDIRECT);
|
||||
@ -561,9 +588,9 @@ describe('Router', () => {
|
||||
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD_REDIRECT]}});
|
||||
|
||||
const futureState = new (RouterStateSnapshot as any)(
|
||||
'url', new TreeNode(
|
||||
empty.root,
|
||||
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||
'url', new TreeNode(empty.root, [
|
||||
new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
|
||||
]));
|
||||
|
||||
checkGuards(futureState, empty, TestBed, (result) => {
|
||||
expect(serializer.serialize(result as UrlTree)).toBe('/' + CA_GRANDCHILD_REDIRECT);
|
||||
@ -592,21 +619,19 @@ describe('Router', () => {
|
||||
const currentState = new (RouterStateSnapshot as any)(
|
||||
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
||||
const futureState = new (RouterStateSnapshot as any)(
|
||||
'url', new TreeNode(
|
||||
empty.root,
|
||||
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||
'url', new TreeNode(empty.root, [
|
||||
new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
|
||||
]));
|
||||
|
||||
checkGuards(futureState, currentState, TestBed, (result) => {
|
||||
expect(serializer.serialize(result as UrlTree)).toBe('/' + CDA_CHILD_REDIRECT);
|
||||
expect(logger.logs).toEqual([CDA_CHILD_REDIRECT]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
|
||||
it('should resolve data', () => {
|
||||
/**
|
||||
* R --> R
|
||||
@ -619,7 +644,7 @@ describe('Router', () => {
|
||||
'url', new TreeNode(empty.root, [new TreeNode(n, [])]));
|
||||
|
||||
checkResolveData(s, empty, inj, () => {
|
||||
expect(s.root.firstChild !.data).toEqual({data: 'resolver_value'});
|
||||
expect(s.root.firstChild!.data).toEqual({data: 'resolver_value'});
|
||||
});
|
||||
});
|
||||
|
||||
@ -634,7 +659,7 @@ describe('Router', () => {
|
||||
const parentResolve = {data: 'resolver'};
|
||||
const childResolve = {};
|
||||
|
||||
const parent = createActivatedRouteSnapshot({component: null !, resolve: parentResolve});
|
||||
const parent = createActivatedRouteSnapshot({component: null!, resolve: parentResolve});
|
||||
const child = createActivatedRouteSnapshot({component: 'b', resolve: childResolve});
|
||||
|
||||
const s = new (RouterStateSnapshot as any)(
|
||||
@ -643,7 +668,7 @@ describe('Router', () => {
|
||||
const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)};
|
||||
|
||||
checkResolveData(s, empty, inj, () => {
|
||||
expect(s.root.firstChild !.firstChild !.data).toEqual({data: 'resolver_value'});
|
||||
expect(s.root.firstChild!.firstChild!.data).toEqual({data: 'resolver_value'});
|
||||
});
|
||||
});
|
||||
|
||||
@ -668,8 +693,8 @@ describe('Router', () => {
|
||||
const s2 = new (RouterStateSnapshot as any)(
|
||||
'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
|
||||
checkResolveData(s2, s1, inj, () => {
|
||||
expect(s2.root.firstChild !.data).toEqual({data: 'resolver1_value'});
|
||||
expect(s2.root.firstChild !.firstChild !.data).toEqual({data: 'resolver2_value'});
|
||||
expect(s2.root.firstChild!.data).toEqual({data: 'resolver1_value'});
|
||||
expect(s2.root.firstChild!.firstChild!.data).toEqual({data: 'resolver2_value'});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -680,27 +705,29 @@ function checkResolveData(
|
||||
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
|
||||
// Since we only test the guards and their resolve data function, we don't need to provide
|
||||
// a full navigation transition object with all properties set.
|
||||
of ({
|
||||
guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())
|
||||
} as NavigationTransition)
|
||||
of({guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())} as
|
||||
NavigationTransition)
|
||||
.pipe(resolveDataOperator('emptyOnly', injector))
|
||||
.subscribe(check, (e) => { throw e; });
|
||||
.subscribe(check, (e) => {
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
function checkGuards(
|
||||
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
|
||||
check: (result: boolean | UrlTree) => void): void {
|
||||
check: (result: boolean|UrlTree) => void): void {
|
||||
// Since we only test the guards, we don't need to provide a full navigation
|
||||
// transition object with all properties set.
|
||||
of ({
|
||||
guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())
|
||||
} as NavigationTransition)
|
||||
of({guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())} as
|
||||
NavigationTransition)
|
||||
.pipe(checkGuardsOperator(injector))
|
||||
.subscribe({
|
||||
next(t) {
|
||||
if (t.guardsResult === null) throw new Error('Guard result expected');
|
||||
return check(t.guardsResult);
|
||||
},
|
||||
error(e) { throw e; }
|
||||
error(e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Compiler, Component, NgModule, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
|
||||
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
import {fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
|
||||
import {PreloadAllModules, PreloadingStrategy, RouterPreloader} from '@angular/router';
|
||||
|
||||
import {Route, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterModule} from '../index';
|
||||
@ -67,8 +67,7 @@ describe('RouterPreloader', () => {
|
||||
const events: Array<RouteConfigLoadStart|RouteConfigLoadEnd> = [];
|
||||
@NgModule({
|
||||
declarations: [LazyLoadedCmp],
|
||||
imports:
|
||||
[RouterModule.forChild([{path: 'LoadedModule2', component: LazyLoadedCmp}])]
|
||||
imports: [RouterModule.forChild([{path: 'LoadedModule2', component: LazyLoadedCmp}])]
|
||||
})
|
||||
class LoadedModule2 {
|
||||
}
|
||||
@ -98,13 +97,13 @@ describe('RouterPreloader', () => {
|
||||
const c = router.config;
|
||||
expect(c[0].loadChildren).toEqual('expected');
|
||||
|
||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig!;
|
||||
const module: any = loadedConfig.module;
|
||||
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
|
||||
expect(module._parent).toBe(testModule);
|
||||
|
||||
const loadedConfig2: LoadedRouterConfig =
|
||||
(loadedConfig.routes[0] as any)._loadedConfig !;
|
||||
(loadedConfig.routes[0] as any)._loadedConfig!;
|
||||
const module2: any = loadedConfig2.module;
|
||||
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
|
||||
expect(module2._parent).toBe(module);
|
||||
@ -126,57 +125,58 @@ describe('RouterPreloader', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', fakeAsync(inject(
|
||||
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler],
|
||||
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader,
|
||||
router: Router, testModule: NgModuleRef<any>, compiler: Compiler) => {
|
||||
@NgModule()
|
||||
class LoadedModule2 {
|
||||
}
|
||||
it('should work',
|
||||
fakeAsync(inject(
|
||||
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler],
|
||||
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
|
||||
testModule: NgModuleRef<any>, compiler: Compiler) => {
|
||||
@NgModule()
|
||||
class LoadedModule2 {
|
||||
}
|
||||
|
||||
const module2 = compiler.compileModuleSync(LoadedModule2).create(null);
|
||||
const module2 = compiler.compileModuleSync(LoadedModule2).create(null);
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
<Route>{
|
||||
path: 'LoadedModule2',
|
||||
loadChildren: 'no',
|
||||
_loadedConfig: {
|
||||
routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}],
|
||||
module: module2,
|
||||
}
|
||||
},
|
||||
])]
|
||||
})
|
||||
class LoadedModule1 {
|
||||
}
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
<Route>{
|
||||
path: 'LoadedModule2',
|
||||
loadChildren: 'no',
|
||||
_loadedConfig: {
|
||||
routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}],
|
||||
module: module2,
|
||||
}
|
||||
},
|
||||
])]
|
||||
})
|
||||
class LoadedModule1 {
|
||||
}
|
||||
|
||||
@NgModule({imports: [RouterModule.forChild([])]})
|
||||
class LoadedModule3 {
|
||||
}
|
||||
@NgModule({imports: [RouterModule.forChild([])]})
|
||||
class LoadedModule3 {
|
||||
}
|
||||
|
||||
loader.stubbedModules = {
|
||||
expected: LoadedModule1,
|
||||
expected3: LoadedModule3,
|
||||
};
|
||||
loader.stubbedModules = {
|
||||
expected: LoadedModule1,
|
||||
expected3: LoadedModule3,
|
||||
};
|
||||
|
||||
preloader.preload().subscribe(() => {});
|
||||
preloader.preload().subscribe(() => {});
|
||||
|
||||
tick();
|
||||
tick();
|
||||
|
||||
const c = router.config;
|
||||
const c = router.config;
|
||||
|
||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
|
||||
const module: any = loadedConfig.module;
|
||||
expect(module._parent).toBe(testModule);
|
||||
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig!;
|
||||
const module: any = loadedConfig.module;
|
||||
expect(module._parent).toBe(testModule);
|
||||
|
||||
const loadedConfig2: LoadedRouterConfig =
|
||||
(loadedConfig.routes[0] as any)._loadedConfig !;
|
||||
const loadedConfig3: LoadedRouterConfig =
|
||||
(loadedConfig2.routes[0] as any)._loadedConfig !;
|
||||
const module3: any = loadedConfig3.module;
|
||||
expect(module3._parent).toBe(module2);
|
||||
})));
|
||||
const loadedConfig2: LoadedRouterConfig =
|
||||
(loadedConfig.routes[0] as any)._loadedConfig!;
|
||||
const loadedConfig3: LoadedRouterConfig =
|
||||
(loadedConfig2.routes[0] as any)._loadedConfig!;
|
||||
const module3: any = loadedConfig3.module;
|
||||
expect(module3._parent).toBe(module2);
|
||||
})));
|
||||
});
|
||||
|
||||
describe('should ignore errors', () => {
|
||||
@ -237,11 +237,11 @@ describe('RouterPreloader', () => {
|
||||
|
||||
tick();
|
||||
|
||||
const c = router.config as{_loadedConfig: LoadedRouterConfig}[];
|
||||
const c = router.config as {_loadedConfig: LoadedRouterConfig}[];
|
||||
expect(c[0]._loadedConfig).toBeDefined();
|
||||
expect(c[0]._loadedConfig !.routes).not.toBe(configs);
|
||||
expect(c[0]._loadedConfig !.routes[0]).not.toBe(configs[0]);
|
||||
expect(c[0]._loadedConfig !.routes[0].component).toBe(configs[0].component);
|
||||
expect(c[0]._loadedConfig!.routes).not.toBe(configs);
|
||||
expect(c[0]._loadedConfig!.routes[0]).not.toBe(configs[0]);
|
||||
expect(c[0]._loadedConfig!.routes[0].component).toBe(configs[0].component);
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
@ -128,7 +128,9 @@ describe('RouterScroller', () => {
|
||||
}, 1000);
|
||||
return r;
|
||||
}))
|
||||
.subscribe((e: Scroll) => { viewportScroller.scrollToPosition(e.position); });
|
||||
.subscribe((e: Scroll) => {
|
||||
viewportScroller.scrollToPosition(e.position);
|
||||
});
|
||||
|
||||
events.next(new NavigationStart(1, '/a'));
|
||||
events.next(new NavigationEnd(1, '/a', '/a'));
|
||||
@ -158,8 +160,8 @@ describe('RouterScroller', () => {
|
||||
|
||||
|
||||
function createRouterScroller({scrollPositionRestoration, anchorScrolling}: {
|
||||
scrollPositionRestoration: 'disabled' | 'enabled' | 'top',
|
||||
anchorScrolling: 'disabled' | 'enabled'
|
||||
scrollPositionRestoration: 'disabled'|'enabled'|'top',
|
||||
anchorScrolling: 'disabled'|'enabled'
|
||||
}) {
|
||||
const events = new Subject<RouterEvent>();
|
||||
const router = <any>{
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, equalParamsAndUrlSegments} from '../src/router_state';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, advanceActivatedRoute, equalParamsAndUrlSegments, RouterState, RouterStateSnapshot} from '../src/router_state';
|
||||
import {Params} from '../src/shared';
|
||||
import {UrlSegment} from '../src/url_tree';
|
||||
import {TreeNode} from '../src/utils/tree';
|
||||
@ -30,7 +30,9 @@ describe('RouterState & Snapshot', () => {
|
||||
state = new (RouterStateSnapshot as any)('url', root);
|
||||
});
|
||||
|
||||
it('should return first child', () => { expect(state.root.firstChild).toBe(b); });
|
||||
it('should return first child', () => {
|
||||
expect(state.root.firstChild).toBe(b);
|
||||
});
|
||||
|
||||
it('should return children', () => {
|
||||
const cc = state.root.children;
|
||||
@ -39,17 +41,17 @@ describe('RouterState & Snapshot', () => {
|
||||
});
|
||||
|
||||
it('should return root', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
expect(b.root).toBe(state.root);
|
||||
});
|
||||
|
||||
it('should return parent', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
expect(b.parent).toBe(state.root);
|
||||
});
|
||||
|
||||
it('should return path from root', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
const p = b.pathFromRoot;
|
||||
expect(p[0]).toBe(state.root);
|
||||
expect(p[1]).toBe(b);
|
||||
@ -72,7 +74,9 @@ describe('RouterState & Snapshot', () => {
|
||||
state = new (RouterState as any)(root, <any>null);
|
||||
});
|
||||
|
||||
it('should return first child', () => { expect(state.root.firstChild).toBe(b); });
|
||||
it('should return first child', () => {
|
||||
expect(state.root.firstChild).toBe(b);
|
||||
});
|
||||
|
||||
it('should return children', () => {
|
||||
const cc = state.root.children;
|
||||
@ -81,17 +85,17 @@ describe('RouterState & Snapshot', () => {
|
||||
});
|
||||
|
||||
it('should return root', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
expect(b.root).toBe(state.root);
|
||||
});
|
||||
|
||||
it('should return parent', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
expect(b.parent).toBe(state.root);
|
||||
});
|
||||
|
||||
it('should return path from root', () => {
|
||||
const b = state.root.firstChild !;
|
||||
const b = state.root.firstChild!;
|
||||
const p = b.pathFromRoot;
|
||||
expect(p[0]).toBe(state.root);
|
||||
expect(p[1]).toBe(b);
|
||||
@ -102,7 +106,7 @@ describe('RouterState & Snapshot', () => {
|
||||
function createSnapshot(params: Params, url: UrlSegment[]): ActivatedRouteSnapshot {
|
||||
const snapshot = new (ActivatedRouteSnapshot as any)(
|
||||
url, params, <any>null, <any>null, <any>null, <any>null, <any>null, <any>null, <any>null,
|
||||
-1, null !);
|
||||
-1, null!);
|
||||
snapshot._routerState = new (RouterStateSnapshot as any)('', new TreeNode(snapshot, []));
|
||||
return snapshot;
|
||||
}
|
||||
@ -166,10 +170,11 @@ describe('RouterState & Snapshot', () => {
|
||||
});
|
||||
|
||||
describe('advanceActivatedRoute', () => {
|
||||
|
||||
let route: ActivatedRoute;
|
||||
|
||||
beforeEach(() => { route = createActivatedRoute('a'); });
|
||||
beforeEach(() => {
|
||||
route = createActivatedRoute('a');
|
||||
});
|
||||
|
||||
function createSnapshot(params: Params, url: UrlSegment[]): ActivatedRouteSnapshot {
|
||||
const queryParams = {};
|
||||
@ -177,7 +182,7 @@ describe('RouterState & Snapshot', () => {
|
||||
const data = {};
|
||||
const snapshot = new (ActivatedRouteSnapshot as any)(
|
||||
url, params, queryParams, fragment, data, <any>null, <any>null, <any>null, <any>null, -1,
|
||||
null !);
|
||||
null!);
|
||||
const state = new (RouterStateSnapshot as any)('', new TreeNode(snapshot, []));
|
||||
snapshot._routerState = state;
|
||||
return snapshot;
|
||||
@ -190,7 +195,9 @@ describe('RouterState & Snapshot', () => {
|
||||
(route as any)._futureSnapshot = secondPlace;
|
||||
|
||||
let hasSeenDataChange = false;
|
||||
route.data.forEach((data) => { hasSeenDataChange = true; });
|
||||
route.data.forEach((data) => {
|
||||
hasSeenDataChange = true;
|
||||
});
|
||||
advanceActivatedRoute(route);
|
||||
expect(hasSeenDataChange).toEqual(true);
|
||||
});
|
||||
@ -200,7 +207,7 @@ describe('RouterState & Snapshot', () => {
|
||||
function createActivatedRouteSnapshot(cmp: string) {
|
||||
return new (ActivatedRouteSnapshot as any)(
|
||||
<any>[], <any>null, <any>null, <any>null, <any>null, <any>null, <any>cmp, <any>null,
|
||||
<any>null, -1, null !);
|
||||
<any>null, -1, null!);
|
||||
}
|
||||
|
||||
function createActivatedRoute(cmp: string) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ParamMap, convertToParamMap} from '../src/shared';
|
||||
import {convertToParamMap, ParamMap} from '../src/shared';
|
||||
|
||||
describe('ParamsMap', () => {
|
||||
it('should returns whether a parameter is present', () => {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {PRIMARY_OUTLET} from '../src/shared';
|
||||
import {DefaultUrlSerializer, UrlSegmentGroup, encodeUriFragment, encodeUriQuery, encodeUriSegment, serializePath} from '../src/url_tree';
|
||||
import {DefaultUrlSerializer, encodeUriFragment, encodeUriQuery, encodeUriSegment, serializePath, UrlSegmentGroup} from '../src/url_tree';
|
||||
|
||||
describe('url serializer', () => {
|
||||
const url = new DefaultUrlSerializer();
|
||||
@ -188,8 +188,8 @@ describe('url serializer', () => {
|
||||
|
||||
describe('encoding/decoding', () => {
|
||||
it('should encode/decode path segments and parameters', () => {
|
||||
const u =
|
||||
`/${encodeUriSegment("one two")};${encodeUriSegment("p 1")}=${encodeUriSegment("v 1")};${encodeUriSegment("p 2")}=${encodeUriSegment("v 2")}`;
|
||||
const u = `/${encodeUriSegment('one two')};${encodeUriSegment('p 1')}=${
|
||||
encodeUriSegment('v 1')};${encodeUriSegment('p 2')}=${encodeUriSegment('v 2')}`;
|
||||
const tree = url.parse(u);
|
||||
|
||||
expect(tree.root.children[PRIMARY_OUTLET].segments[0].path).toEqual('one two');
|
||||
@ -199,8 +199,8 @@ describe('url serializer', () => {
|
||||
});
|
||||
|
||||
it('should encode/decode "slash" in path segments and parameters', () => {
|
||||
const u =
|
||||
`/${encodeUriSegment("one/two")};${encodeUriSegment("p/1")}=${encodeUriSegment("v/1")}/three`;
|
||||
const u = `/${encodeUriSegment('one/two')};${encodeUriSegment('p/1')}=${
|
||||
encodeUriSegment('v/1')}/three`;
|
||||
const tree = url.parse(u);
|
||||
const segment = tree.root.children[PRIMARY_OUTLET].segments[0];
|
||||
expect(segment.path).toEqual('one/two');
|
||||
@ -211,8 +211,8 @@ describe('url serializer', () => {
|
||||
});
|
||||
|
||||
it('should encode/decode query params', () => {
|
||||
const u =
|
||||
`/one?${encodeUriQuery("p 1")}=${encodeUriQuery("v 1")}&${encodeUriQuery("p 2")}=${encodeUriQuery("v 2")}`;
|
||||
const u = `/one?${encodeUriQuery('p 1')}=${encodeUriQuery('v 1')}&${encodeUriQuery('p 2')}=${
|
||||
encodeUriQuery('v 2')}`;
|
||||
const tree = url.parse(u);
|
||||
|
||||
expect(tree.queryParams).toEqual({'p 1': 'v 1', 'p 2': 'v 2'});
|
||||
@ -263,7 +263,6 @@ describe('url serializer', () => {
|
||||
});
|
||||
|
||||
describe('special character encoding/decoding', () => {
|
||||
|
||||
// Tests specific to https://github.com/angular/angular/issues/10280
|
||||
it('should parse encoded parens in matrix params', () => {
|
||||
const auxRoutesUrl = '/abc;foo=(other:val)';
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DefaultUrlSerializer, containsTree} from '../src/url_tree';
|
||||
import {containsTree, DefaultUrlSerializer} from '../src/url_tree';
|
||||
|
||||
describe('UrlTree', () => {
|
||||
const serializer = new DefaultUrlSerializer();
|
||||
@ -14,7 +14,9 @@ describe('UrlTree', () => {
|
||||
describe('DefaultUrlSerializer', () => {
|
||||
let serializer: DefaultUrlSerializer;
|
||||
|
||||
beforeEach(() => { serializer = new DefaultUrlSerializer(); });
|
||||
beforeEach(() => {
|
||||
serializer = new DefaultUrlSerializer();
|
||||
});
|
||||
|
||||
it('should parse query parameters', () => {
|
||||
const tree = serializer.parse('/path/to?k=v&k/(a;b)=c');
|
||||
|
@ -21,8 +21,8 @@ describe('tree', () => {
|
||||
});
|
||||
|
||||
it('should return the parent of a node (second child)', () => {
|
||||
const t = new Tree<any>(
|
||||
new TreeNode<number>(1, [new TreeNode<number>(2, []), new TreeNode<number>(3, [])])) as any;
|
||||
const t = new Tree<any>(new TreeNode<number>(
|
||||
1, [new TreeNode<number>(2, []), new TreeNode<number>(3, [])])) as any;
|
||||
expect(t.parent(1)).toEqual(null);
|
||||
expect(t.parent(3)).toEqual(1);
|
||||
});
|
||||
@ -40,8 +40,8 @@ describe('tree', () => {
|
||||
});
|
||||
|
||||
it('should return the siblings of a node', () => {
|
||||
const t = new Tree<any>(
|
||||
new TreeNode<number>(1, [new TreeNode<number>(2, []), new TreeNode<number>(3, [])])) as any;
|
||||
const t = new Tree<any>(new TreeNode<number>(
|
||||
1, [new TreeNode<number>(2, []), new TreeNode<number>(3, [])])) as any;
|
||||
expect(t.siblings(2)).toEqual([3]);
|
||||
expect(t.siblings(1)).toEqual([]);
|
||||
});
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Location, LocationStrategy} from '@angular/common';
|
||||
import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
|
||||
import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core';
|
||||
import {ChildrenOutletContexts, ExtraOptions, NoPreloading, PreloadingStrategy, ROUTER_CONFIGURATION, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router';
|
||||
import {ChildrenOutletContexts, ExtraOptions, NoPreloading, PreloadingStrategy, provideRoutes, Route, Router, ROUTER_CONFIGURATION, RouterModule, ROUTES, Routes, UrlHandlingStrategy, UrlSerializer, ɵflatten as flatten, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS} from '@angular/router';
|
||||
|
||||
|
||||
|
||||
@ -63,7 +63,9 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
/**
|
||||
* @docsNotRequired
|
||||
*/
|
||||
get stubbedModules(): {[path: string]: any} { return this._stubbedModules; }
|
||||
get stubbedModules(): {[path: string]: any} {
|
||||
return this._stubbedModules;
|
||||
}
|
||||
|
||||
constructor(private compiler: Compiler) {}
|
||||
|
||||
@ -76,8 +78,8 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
function isUrlHandlingStrategy(opts: ExtraOptions | UrlHandlingStrategy):
|
||||
opts is UrlHandlingStrategy {
|
||||
function isUrlHandlingStrategy(opts: ExtraOptions|
|
||||
UrlHandlingStrategy): opts is UrlHandlingStrategy {
|
||||
// This property check is needed because UrlHandlingStrategy is an interface and doesn't exist at
|
||||
// runtime.
|
||||
return 'shouldProcessUrl' in opts;
|
||||
@ -113,9 +115,9 @@ export function setupTestingRouter(
|
||||
export function setupTestingRouter(
|
||||
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
|
||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
||||
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||
opts?: ExtraOptions|UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||
const router = new Router(
|
||||
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
||||
null!, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
||||
if (opts) {
|
||||
// Handle deprecated argument ordering.
|
||||
if (isUrlHandlingStrategy(opts)) {
|
||||
|
@ -37,7 +37,7 @@ import {UpgradeModule} from '@angular/upgrade/static';
|
||||
export const RouterUpgradeInitializer = {
|
||||
provide: APP_BOOTSTRAP_LISTENER,
|
||||
multi: true,
|
||||
useFactory: locationSyncBootstrapListener as(ngUpgrade: UpgradeModule) => () => void,
|
||||
useFactory: locationSyncBootstrapListener as (ngUpgrade: UpgradeModule) => () => void,
|
||||
deps: [UpgradeModule]
|
||||
};
|
||||
|
||||
@ -45,7 +45,9 @@ export const RouterUpgradeInitializer = {
|
||||
* @internal
|
||||
*/
|
||||
export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
|
||||
return () => { setUpLocationSync(ngUpgrade); };
|
||||
return () => {
|
||||
setUpLocationSync(ngUpgrade);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,7 +62,7 @@ export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function setUpLocationSync(ngUpgrade: UpgradeModule, urlType: 'path' | 'hash' = 'path') {
|
||||
export function setUpLocationSync(ngUpgrade: UpgradeModule, urlType: 'path'|'hash' = 'path') {
|
||||
if (!ngUpgrade.$injector) {
|
||||
throw new Error(`
|
||||
RouterUpgradeInitializer can be used only after UpgradeModule.bootstrap has been called.
|
||||
|
@ -119,8 +119,7 @@ describe('setUpLocationSync', () => {
|
||||
|
||||
expect(LocationMock.normalize).toHaveBeenCalledWith('/foo/bar');
|
||||
} finally {
|
||||
Object.defineProperty(anchorProto, 'pathname', originalDescriptor !);
|
||||
Object.defineProperty(anchorProto, 'pathname', originalDescriptor!);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
Reference in New Issue
Block a user