feat(router): add events tracking activation of individual routes
* Adds `ChildActivationStart` and `ChildActivationEnd` * Adds test to verify the PreActivation phase of routing
This commit is contained in:
parent
82b067fc40
commit
49cd8513e4
|
@ -10,17 +10,66 @@ import {Route} from './config';
|
||||||
import {RouterStateSnapshot} from './router_state';
|
import {RouterStateSnapshot} from './router_state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Represents an event triggered when a navigation starts.
|
* @whatItDoes Base for events the Router goes through, as opposed to events tied to a specific
|
||||||
|
* Route. `RouterEvent`s will only be fired one time for any given navigation.
|
||||||
*
|
*
|
||||||
* @stable
|
* Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class MyService {
|
||||||
|
* constructor(public router: Router, logger: Logger) {
|
||||||
|
* router.events.filter(e => e instanceof RouterEvent).subscribe(e => {
|
||||||
|
* logger.log(e.id, e.url);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class NavigationStart {
|
export class RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
public id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string) {}
|
public url: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Base for events tied to a specific `Route`, as opposed to events for the Router
|
||||||
|
* lifecycle. `RouteEvent`s may be fired multiple times during a single navigation and will
|
||||||
|
* always receive the `Route` they pertain to.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class MyService {
|
||||||
|
* constructor(public router: Router, spinner: Spinner) {
|
||||||
|
* router.events.filter(e => e instanceof RouteEvent).subscribe(e => {
|
||||||
|
* if (e instanceof ChildActivationStart) {
|
||||||
|
* spinner.start(e.route);
|
||||||
|
* } else if (e instanceof ChildActivationEnd) {
|
||||||
|
* spinner.end(e.route);
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class RouteEvent {
|
||||||
|
constructor(
|
||||||
|
/** @docsNotRequired */
|
||||||
|
public route: Route) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Represents an event triggered when a navigation starts.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export class NavigationStart extends RouterEvent {
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
toString(): string { return `NavigationStart(id: ${this.id}, url: '${this.url}')`; }
|
toString(): string { return `NavigationStart(id: ${this.id}, url: '${this.url}')`; }
|
||||||
}
|
}
|
||||||
|
@ -30,14 +79,16 @@ export class NavigationStart {
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export class NavigationEnd {
|
export class NavigationEnd extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string) {}
|
public urlAfterRedirects: string) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -50,14 +101,16 @@ export class NavigationEnd {
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export class NavigationCancel {
|
export class NavigationCancel extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public reason: string) {}
|
public reason: string) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
|
toString(): string { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
|
||||||
|
@ -68,14 +121,16 @@ export class NavigationCancel {
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export class NavigationError {
|
export class NavigationError extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public error: any) {}
|
public error: any) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -88,16 +143,18 @@ export class NavigationError {
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export class RoutesRecognized {
|
export class RoutesRecognized extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string,
|
public urlAfterRedirects: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public state: RouterStateSnapshot) {}
|
public state: RouterStateSnapshot) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -105,43 +162,23 @@ export class RoutesRecognized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Represents an event triggered before lazy loading a route config.
|
|
||||||
*
|
|
||||||
* @experimental
|
|
||||||
*/
|
|
||||||
export class RouteConfigLoadStart {
|
|
||||||
constructor(public route: Route) {}
|
|
||||||
|
|
||||||
toString(): string { return `RouteConfigLoadStart(path: ${this.route.path})`; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Represents an event triggered when a route has been lazy loaded.
|
|
||||||
*
|
|
||||||
* @experimental
|
|
||||||
*/
|
|
||||||
export class RouteConfigLoadEnd {
|
|
||||||
constructor(public route: Route) {}
|
|
||||||
|
|
||||||
toString(): string { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Represents the start of the Guard phase of routing.
|
* @whatItDoes Represents the start of the Guard phase of routing.
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class GuardsCheckStart {
|
export class GuardsCheckStart extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string,
|
public urlAfterRedirects: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public state: RouterStateSnapshot) {}
|
public state: RouterStateSnapshot) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
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})`;
|
||||||
|
@ -153,18 +190,20 @@ export class GuardsCheckStart {
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class GuardsCheckEnd {
|
export class GuardsCheckEnd extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string,
|
public urlAfterRedirects: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public state: RouterStateSnapshot,
|
public state: RouterStateSnapshot,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public shouldActivate: boolean) {}
|
public shouldActivate: boolean) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
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})`;
|
||||||
|
@ -179,16 +218,18 @@ export class GuardsCheckEnd {
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class ResolveStart {
|
export class ResolveStart extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string,
|
public urlAfterRedirects: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public state: RouterStateSnapshot) {}
|
public state: RouterStateSnapshot) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
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})`;
|
||||||
|
@ -201,22 +242,62 @@ export class ResolveStart {
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export class ResolveEnd {
|
export class ResolveEnd extends RouterEvent {
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public id: number,
|
id: number,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public url: string,
|
url: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public urlAfterRedirects: string,
|
public urlAfterRedirects: string,
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
public state: RouterStateSnapshot) {}
|
public state: RouterStateSnapshot) {
|
||||||
|
super(id, url);
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
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})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Represents an event triggered before lazy loading a route config.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class RouteConfigLoadStart extends RouteEvent {
|
||||||
|
toString(): string { return `RouteConfigLoadStart(path: ${this.route.path})`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Represents an event triggered when a route has been lazy loaded.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class RouteConfigLoadEnd extends RouteEvent {
|
||||||
|
toString(): string { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Represents the start of end of the Resolve phase of routing. See note on
|
||||||
|
* {@link ChildActivationEnd} for use of this experimental API.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class ChildActivationStart extends RouteEvent {
|
||||||
|
toString(): string { return `ChildActivationStart(path: '${this.route.path}')`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Represents the start of end of the Resolve phase of routing. See note on
|
||||||
|
* {@link ChildActivationStart} for use of this experimental API.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class ChildActivationEnd extends RouteEvent {
|
||||||
|
toString(): string { return `ChildActivationEnd(path: '${this.route.path}')`; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Represents a router event, allowing you to track the lifecycle of the router.
|
* @whatItDoes Represents a router event, allowing you to track the lifecycle of the router.
|
||||||
*
|
*
|
||||||
|
@ -227,15 +308,15 @@ export class ResolveEnd {
|
||||||
* - {@link RouteConfigLoadEnd},
|
* - {@link RouteConfigLoadEnd},
|
||||||
* - {@link RoutesRecognized},
|
* - {@link RoutesRecognized},
|
||||||
* - {@link GuardsCheckStart},
|
* - {@link GuardsCheckStart},
|
||||||
|
* - {@link ChildActivationStart},
|
||||||
* - {@link GuardsCheckEnd},
|
* - {@link GuardsCheckEnd},
|
||||||
* - {@link ResolveStart},
|
* - {@link ResolveStart},
|
||||||
* - {@link ResolveEnd},
|
* - {@link ResolveEnd},
|
||||||
|
* - {@link ChildActivationEnd}
|
||||||
* - {@link NavigationEnd},
|
* - {@link NavigationEnd},
|
||||||
* - {@link NavigationCancel},
|
* - {@link NavigationCancel},
|
||||||
* - {@link NavigationError}
|
* - {@link NavigationError}
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError |
|
export type Event = RouterEvent | RouteEvent;
|
||||||
RoutesRecognized | RouteConfigLoadStart | RouteConfigLoadEnd | GuardsCheckStart |
|
|
||||||
GuardsCheckEnd | ResolveStart | ResolveEnd;
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, Routes, Ru
|
||||||
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||||
export {RouterLinkActive} from './directives/router_link_active';
|
export {RouterLinkActive} from './directives/router_link_active';
|
||||||
export {RouterOutlet} from './directives/router_outlet';
|
export {RouterOutlet} from './directives/router_outlet';
|
||||||
export {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
export {ChildActivationEnd, ChildActivationStart, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RoutesRecognized} from './events';
|
||||||
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './interfaces';
|
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './interfaces';
|
||||||
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
export {NavigationExtras, Router} from './router';
|
export {NavigationExtras, Router} from './router';
|
||||||
|
|
|
@ -0,0 +1,347 @@
|
||||||
|
/**
|
||||||
|
* @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 {Injector} from '@angular/core';
|
||||||
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
import {from} from 'rxjs/observable/from';
|
||||||
|
import {of } from 'rxjs/observable/of';
|
||||||
|
import {concatMap} from 'rxjs/operator/concatMap';
|
||||||
|
import {every} from 'rxjs/operator/every';
|
||||||
|
import {first} from 'rxjs/operator/first';
|
||||||
|
import {last} from 'rxjs/operator/last';
|
||||||
|
import {map} from 'rxjs/operator/map';
|
||||||
|
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||||
|
import {reduce} from 'rxjs/operator/reduce';
|
||||||
|
|
||||||
|
import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config';
|
||||||
|
import {ChildActivationStart, RouteEvent} from './events';
|
||||||
|
import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
||||||
|
import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
|
||||||
|
import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection';
|
||||||
|
import {TreeNode, nodeChildrenAsMap} from './utils/tree';
|
||||||
|
|
||||||
|
class CanActivate {
|
||||||
|
constructor(public path: ActivatedRouteSnapshot[]) {}
|
||||||
|
get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CanDeactivate {
|
||||||
|
constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class bundles the actions involved in preactivation of a route.
|
||||||
|
*/
|
||||||
|
export class PreActivation {
|
||||||
|
private canActivateChecks: CanActivate[] = [];
|
||||||
|
private canDeactivateChecks: CanDeactivate[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
||||||
|
private moduleInjector: Injector, private forwardEvent?: (evt: RouteEvent) => void) {}
|
||||||
|
|
||||||
|
initalize(parentContexts: ChildrenOutletContexts): void {
|
||||||
|
const futureRoot = this.future._root;
|
||||||
|
const currRoot = this.curr ? this.curr._root : null;
|
||||||
|
this.setupChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkGuards(): Observable<boolean> {
|
||||||
|
if (!this.isDeactivating() && !this.isActivating()) {
|
||||||
|
return of (true);
|
||||||
|
}
|
||||||
|
const canDeactivate$ = this.runCanDeactivateChecks();
|
||||||
|
return mergeMap.call(
|
||||||
|
canDeactivate$,
|
||||||
|
(canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveData(): Observable<any> {
|
||||||
|
if (!this.isActivating()) return of (null);
|
||||||
|
const checks$ = from(this.canActivateChecks);
|
||||||
|
const runningChecks$ =
|
||||||
|
concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route));
|
||||||
|
return reduce.call(runningChecks$, (_: any, __: any) => _);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeactivating(): boolean { return this.canDeactivateChecks.length !== 0; }
|
||||||
|
|
||||||
|
isActivating(): boolean { return this.canActivateChecks.length !== 0; }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over child routes and calls recursive `setupRouteGuards` to get `this` instance in
|
||||||
|
* proper state to run `checkGuards()` method.
|
||||||
|
*/
|
||||||
|
private setupChildRouteGuards(
|
||||||
|
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
|
||||||
|
contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
|
||||||
|
const prevChildren = nodeChildrenAsMap(currNode);
|
||||||
|
|
||||||
|
// Process the children of the future route
|
||||||
|
futureNode.children.forEach(c => {
|
||||||
|
this.setupRouteGuards(
|
||||||
|
c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
|
||||||
|
delete prevChildren[c.value.outlet];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process any children left from the current route (not active for the future route)
|
||||||
|
forEach(
|
||||||
|
prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) =>
|
||||||
|
this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over child routes and calls recursive `setupRouteGuards` to get `this` instance in
|
||||||
|
* proper state to run `checkGuards()` method.
|
||||||
|
*/
|
||||||
|
private setupRouteGuards(
|
||||||
|
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
|
||||||
|
parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
|
||||||
|
const future = futureNode.value;
|
||||||
|
const curr = currNode ? currNode.value : null;
|
||||||
|
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
|
||||||
|
|
||||||
|
// reusing the node
|
||||||
|
if (curr && future._routeConfig === curr._routeConfig) {
|
||||||
|
const shouldRunGuardsAndResolvers = this.shouldRunGuardsAndResolvers(
|
||||||
|
curr, future, future._routeConfig !.runGuardsAndResolvers);
|
||||||
|
if (shouldRunGuardsAndResolvers) {
|
||||||
|
this.canActivateChecks.push(new CanActivate(futurePath));
|
||||||
|
} else {
|
||||||
|
// we need to set the data
|
||||||
|
future.data = curr.data;
|
||||||
|
future._resolvedData = curr._resolvedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a component, we need to go through an outlet.
|
||||||
|
if (future.component) {
|
||||||
|
this.setupChildRouteGuards(
|
||||||
|
futureNode, currNode, context ? context.children : null, futurePath);
|
||||||
|
|
||||||
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
|
} else {
|
||||||
|
this.setupChildRouteGuards(futureNode, currNode, parentContexts, futurePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRunGuardsAndResolvers) {
|
||||||
|
const outlet = context !.outlet !;
|
||||||
|
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (curr) {
|
||||||
|
this.deactivateRouteAndItsChildren(currNode, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canActivateChecks.push(new CanActivate(futurePath));
|
||||||
|
// If we have a component, we need to go through an outlet.
|
||||||
|
if (future.component) {
|
||||||
|
this.setupChildRouteGuards(futureNode, null, context ? context.children : null, futurePath);
|
||||||
|
|
||||||
|
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||||
|
} else {
|
||||||
|
this.setupChildRouteGuards(futureNode, null, parentContexts, futurePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldRunGuardsAndResolvers(
|
||||||
|
curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot,
|
||||||
|
mode: RunGuardsAndResolvers|undefined): boolean {
|
||||||
|
switch (mode) {
|
||||||
|
case 'always':
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'paramsOrQueryParamsChange':
|
||||||
|
return !equalParamsAndUrlSegments(curr, future) ||
|
||||||
|
!shallowEqual(curr.queryParams, future.queryParams);
|
||||||
|
|
||||||
|
case 'paramsChange':
|
||||||
|
default:
|
||||||
|
return !equalParamsAndUrlSegments(curr, future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private deactivateRouteAndItsChildren(
|
||||||
|
route: TreeNode<ActivatedRouteSnapshot>, context: OutletContext|null): void {
|
||||||
|
const children = nodeChildrenAsMap(route);
|
||||||
|
const r = route.value;
|
||||||
|
|
||||||
|
forEach(children, (node: TreeNode<ActivatedRouteSnapshot>, childName: string) => {
|
||||||
|
if (!r.component) {
|
||||||
|
this.deactivateRouteAndItsChildren(node, context);
|
||||||
|
} else if (context) {
|
||||||
|
this.deactivateRouteAndItsChildren(node, context.children.getContext(childName));
|
||||||
|
} else {
|
||||||
|
this.deactivateRouteAndItsChildren(node, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!r.component) {
|
||||||
|
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
||||||
|
} else if (context && context.outlet && context.outlet.isActivated) {
|
||||||
|
this.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
|
||||||
|
} else {
|
||||||
|
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private runCanDeactivateChecks(): Observable<boolean> {
|
||||||
|
const checks$ = from(this.canDeactivateChecks);
|
||||||
|
const runningChecks$ = mergeMap.call(
|
||||||
|
checks$, (check: CanDeactivate) => this.runCanDeactivate(check.component, check.route));
|
||||||
|
return every.call(runningChecks$, (result: boolean) => result === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runCanActivateChecks(): Observable<boolean> {
|
||||||
|
const checks$ = from(this.canActivateChecks);
|
||||||
|
const runningChecks$ = concatMap.call(
|
||||||
|
checks$, (check: CanActivate) => andObservables(from([
|
||||||
|
this.fireChildActivationStart(check.path), this.runCanActivateChild(check.path),
|
||||||
|
this.runCanActivate(check.route)
|
||||||
|
])));
|
||||||
|
return every.call(runningChecks$, (result: boolean) => result === true);
|
||||||
|
// this.fireChildActivationStart(check.path),
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private fireChildActivationStart(path: ActivatedRouteSnapshot[]): Observable<boolean> {
|
||||||
|
if (!this.forwardEvent) return of (true);
|
||||||
|
const childActivations = path.slice(0, path.length - 1).reverse().filter(_ => _ !== null);
|
||||||
|
|
||||||
|
return andObservables(map.call(from(childActivations), (snapshot: ActivatedRouteSnapshot) => {
|
||||||
|
if (this.forwardEvent && snapshot._routeConfig) {
|
||||||
|
this.forwardEvent(new ChildActivationStart(snapshot._routeConfig));
|
||||||
|
}
|
||||||
|
return of (true);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
private runCanActivate(future: ActivatedRouteSnapshot): Observable<boolean> {
|
||||||
|
const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
|
||||||
|
if (!canActivate || canActivate.length === 0) return of (true);
|
||||||
|
const obs = map.call(from(canActivate), (c: any) => {
|
||||||
|
const guard = this.getToken(c, future);
|
||||||
|
let observable: Observable<boolean>;
|
||||||
|
if (guard.canActivate) {
|
||||||
|
observable = wrapIntoObservable(guard.canActivate(future, this.future));
|
||||||
|
} else {
|
||||||
|
observable = wrapIntoObservable(guard(future, this.future));
|
||||||
|
}
|
||||||
|
return first.call(observable);
|
||||||
|
});
|
||||||
|
return andObservables(obs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable<boolean> {
|
||||||
|
const future = path[path.length - 1];
|
||||||
|
|
||||||
|
const canActivateChildGuards = path.slice(0, path.length - 1)
|
||||||
|
.reverse()
|
||||||
|
.map(p => this.extractCanActivateChild(p))
|
||||||
|
.filter(_ => _ !== null);
|
||||||
|
|
||||||
|
return andObservables(map.call(from(canActivateChildGuards), (d: any) => {
|
||||||
|
const obs = map.call(from(d.guards), (c: any) => {
|
||||||
|
const guard = this.getToken(c, d.node);
|
||||||
|
let observable: Observable<boolean>;
|
||||||
|
if (guard.canActivateChild) {
|
||||||
|
observable = wrapIntoObservable(guard.canActivateChild(future, this.future));
|
||||||
|
} else {
|
||||||
|
observable = wrapIntoObservable(guard(future, this.future));
|
||||||
|
}
|
||||||
|
return first.call(observable);
|
||||||
|
});
|
||||||
|
return andObservables(obs);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractCanActivateChild(p: ActivatedRouteSnapshot):
|
||||||
|
{node: ActivatedRouteSnapshot, guards: any[]}|null {
|
||||||
|
const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
|
||||||
|
if (!canActivateChild || canActivateChild.length === 0) return null;
|
||||||
|
return {node: p, guards: canActivateChild};
|
||||||
|
}
|
||||||
|
|
||||||
|
private runCanDeactivate(component: Object|null, curr: ActivatedRouteSnapshot):
|
||||||
|
Observable<boolean> {
|
||||||
|
const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
|
||||||
|
if (!canDeactivate || canDeactivate.length === 0) return of (true);
|
||||||
|
const canDeactivate$ = mergeMap.call(from(canDeactivate), (c: any) => {
|
||||||
|
const guard = this.getToken(c, curr);
|
||||||
|
let observable: Observable<boolean>;
|
||||||
|
if (guard.canDeactivate) {
|
||||||
|
observable =
|
||||||
|
wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future));
|
||||||
|
} else {
|
||||||
|
observable = wrapIntoObservable(guard(component, curr, this.curr, this.future));
|
||||||
|
}
|
||||||
|
return first.call(observable);
|
||||||
|
});
|
||||||
|
return every.call(canDeactivate$, (result: any) => result === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runResolve(future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const resolve = future._resolve;
|
||||||
|
return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
|
||||||
|
future._resolvedData = resolvedData;
|
||||||
|
future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve};
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const keys = Object.keys(resolve);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return of ({});
|
||||||
|
}
|
||||||
|
if (keys.length === 1) {
|
||||||
|
const key = keys[0];
|
||||||
|
return map.call(
|
||||||
|
this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
|
||||||
|
}
|
||||||
|
const data: {[k: string]: any} = {};
|
||||||
|
const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
|
||||||
|
return map.call(this.getResolver(resolve[key], future), (value: any) => {
|
||||||
|
data[key] = value;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return map.call(last.call(runningResolvers$), () => data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const resolver = this.getToken(injectionToken, future);
|
||||||
|
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
||||||
|
wrapIntoObservable(resolver(future, this.future));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
||||||
|
const config = closestLoadedConfig(snapshot);
|
||||||
|
const injector = config ? config.module.injector : this.moduleInjector;
|
||||||
|
return injector.get(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null {
|
||||||
|
if (!snapshot) return null;
|
||||||
|
|
||||||
|
for (let s = snapshot.parent; s; s = s.parent) {
|
||||||
|
const route = s._routeConfig;
|
||||||
|
if (route && route._loadedConfig) return route._loadedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -12,31 +12,27 @@ import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Subject} from 'rxjs/Subject';
|
import {Subject} from 'rxjs/Subject';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
import {from} from 'rxjs/observable/from';
|
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
import {concatMap} from 'rxjs/operator/concatMap';
|
import {concatMap} from 'rxjs/operator/concatMap';
|
||||||
import {every} from 'rxjs/operator/every';
|
|
||||||
import {first} from 'rxjs/operator/first';
|
|
||||||
import {last} from 'rxjs/operator/last';
|
|
||||||
import {map} from 'rxjs/operator/map';
|
import {map} from 'rxjs/operator/map';
|
||||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||||
import {reduce} from 'rxjs/operator/reduce';
|
|
||||||
|
|
||||||
import {applyRedirects} from './apply_redirects';
|
import {applyRedirects} from './apply_redirects';
|
||||||
import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config';
|
import {LoadedRouterConfig, QueryParamsHandling, Route, Routes, validateConfig} from './config';
|
||||||
import {createRouterState} from './create_router_state';
|
import {createRouterState} from './create_router_state';
|
||||||
import {createUrlTree} from './create_url_tree';
|
import {createUrlTree} from './create_url_tree';
|
||||||
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
import {ChildActivationEnd, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RoutesRecognized} from './events';
|
||||||
|
import {PreActivation} from './pre_activation';
|
||||||
import {recognize} from './recognize';
|
import {recognize} from './recognize';
|
||||||
import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
|
import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
import {RouterConfigLoader} from './router_config_loader';
|
import {RouterConfigLoader} from './router_config_loader';
|
||||||
import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
import {ChildrenOutletContexts} from './router_outlet_context';
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
|
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||||
import {Params, isNavigationCancelingError} from './shared';
|
import {Params, isNavigationCancelingError} from './shared';
|
||||||
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
||||||
import {andObservables, forEach, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
import {forEach} from './utils/collection';
|
||||||
import {TreeNode} from './utils/tree';
|
import {TreeNode, nodeChildrenAsMap} from './utils/tree';
|
||||||
|
|
||||||
declare let Zone: any;
|
declare let Zone: any;
|
||||||
|
|
||||||
|
@ -626,18 +622,19 @@ export class Router {
|
||||||
// run preactivation: guards and data resolvers
|
// run preactivation: guards and data resolvers
|
||||||
let preActivation: PreActivation;
|
let preActivation: PreActivation;
|
||||||
|
|
||||||
const preactivationTraverse$ = map.call(
|
const preactivationSetup$ = map.call(
|
||||||
beforePreactivationDone$,
|
beforePreactivationDone$,
|
||||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||||
const moduleInjector = this.ngModule.injector;
|
const moduleInjector = this.ngModule.injector;
|
||||||
preActivation =
|
preActivation = new PreActivation(
|
||||||
new PreActivation(snapshot, this.currentRouterState.snapshot, moduleInjector);
|
snapshot, this.currentRouterState.snapshot, moduleInjector,
|
||||||
preActivation.traverse(this.rootContexts);
|
(evt: RouteEvent) => this.triggerEvent(evt));
|
||||||
|
preActivation.initalize(this.rootContexts);
|
||||||
return {appliedUrl, snapshot};
|
return {appliedUrl, snapshot};
|
||||||
});
|
});
|
||||||
|
|
||||||
const preactivationCheckGuards$ = mergeMap.call(
|
const preactivationCheckGuards$ = mergeMap.call(
|
||||||
preactivationTraverse$,
|
preactivationSetup$,
|
||||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||||
if (this.navigationId !== id) return of (false);
|
if (this.navigationId !== id) return of (false);
|
||||||
|
|
||||||
|
@ -715,7 +712,8 @@ export class Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new ActivateRoutes(this.routeReuseStrategy, state, storedState)
|
new ActivateRoutes(
|
||||||
|
this.routeReuseStrategy, state, storedState, (evt: Event) => this.triggerEvent(evt))
|
||||||
.activate(this.rootContexts);
|
.activate(this.rootContexts);
|
||||||
|
|
||||||
navigationIsSuccessful = true;
|
navigationIsSuccessful = true;
|
||||||
|
@ -763,289 +761,10 @@ export class Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CanActivate {
|
|
||||||
constructor(public path: ActivatedRouteSnapshot[]) {}
|
|
||||||
get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; }
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanDeactivate {
|
|
||||||
constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PreActivation {
|
|
||||||
private canActivateChecks: CanActivate[] = [];
|
|
||||||
private canDeactivateChecks: CanDeactivate[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private future: RouterStateSnapshot, private curr: RouterStateSnapshot,
|
|
||||||
private moduleInjector: Injector) {}
|
|
||||||
|
|
||||||
traverse(parentContexts: ChildrenOutletContexts): void {
|
|
||||||
const futureRoot = this.future._root;
|
|
||||||
const currRoot = this.curr ? this.curr._root : null;
|
|
||||||
this.traverseChildRoutes(futureRoot, currRoot, parentContexts, [futureRoot.value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(jasonaden): Refactor checkGuards and resolveData so they can collect the checks
|
|
||||||
// and guards before mapping into the observable. Likely remove the observable completely
|
|
||||||
// and make these pure functions so they are more predictable and don't rely on so much
|
|
||||||
// external state.
|
|
||||||
checkGuards(): Observable<boolean> {
|
|
||||||
if (!this.isDeactivating() && !this.isActivating()) {
|
|
||||||
return of (true);
|
|
||||||
}
|
|
||||||
const canDeactivate$ = this.runCanDeactivateChecks();
|
|
||||||
return mergeMap.call(
|
|
||||||
canDeactivate$,
|
|
||||||
(canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveData(): Observable<any> {
|
|
||||||
if (!this.isActivating()) return of (null);
|
|
||||||
const checks$ = from(this.canActivateChecks);
|
|
||||||
const runningChecks$ =
|
|
||||||
concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route));
|
|
||||||
return reduce.call(runningChecks$, (_: any, __: any) => _);
|
|
||||||
}
|
|
||||||
|
|
||||||
isDeactivating(): boolean { return this.canDeactivateChecks.length !== 0; }
|
|
||||||
|
|
||||||
isActivating(): boolean { return this.canActivateChecks.length !== 0; }
|
|
||||||
|
|
||||||
private traverseChildRoutes(
|
|
||||||
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
|
|
||||||
contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
|
|
||||||
const prevChildren = nodeChildrenAsMap(currNode);
|
|
||||||
|
|
||||||
// Process the children of the future route
|
|
||||||
futureNode.children.forEach(c => {
|
|
||||||
this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
|
|
||||||
delete prevChildren[c.value.outlet];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process any children left from the current route (not active for the future route)
|
|
||||||
forEach(
|
|
||||||
prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) =>
|
|
||||||
this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private traverseRoutes(
|
|
||||||
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
|
|
||||||
parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
|
|
||||||
const future = futureNode.value;
|
|
||||||
const curr = currNode ? currNode.value : null;
|
|
||||||
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
|
|
||||||
|
|
||||||
// reusing the node
|
|
||||||
if (curr && future._routeConfig === curr._routeConfig) {
|
|
||||||
const shouldRunGuardsAndResolvers = this.shouldRunGuardsAndResolvers(
|
|
||||||
curr, future, future._routeConfig !.runGuardsAndResolvers);
|
|
||||||
if (shouldRunGuardsAndResolvers) {
|
|
||||||
this.canActivateChecks.push(new CanActivate(futurePath));
|
|
||||||
} else {
|
|
||||||
// we need to set the data
|
|
||||||
future.data = curr.data;
|
|
||||||
future._resolvedData = curr._resolvedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a component, we need to go through an outlet.
|
|
||||||
if (future.component) {
|
|
||||||
this.traverseChildRoutes(
|
|
||||||
futureNode, currNode, context ? context.children : null, futurePath);
|
|
||||||
|
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
|
||||||
} else {
|
|
||||||
this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRunGuardsAndResolvers) {
|
|
||||||
const outlet = context !.outlet !;
|
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (curr) {
|
|
||||||
this.deactivateRouteAndItsChildren(currNode, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canActivateChecks.push(new CanActivate(futurePath));
|
|
||||||
// If we have a component, we need to go through an outlet.
|
|
||||||
if (future.component) {
|
|
||||||
this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);
|
|
||||||
|
|
||||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
|
||||||
} else {
|
|
||||||
this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private shouldRunGuardsAndResolvers(
|
|
||||||
curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot,
|
|
||||||
mode: RunGuardsAndResolvers|undefined): boolean {
|
|
||||||
switch (mode) {
|
|
||||||
case 'always':
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case 'paramsOrQueryParamsChange':
|
|
||||||
return !equalParamsAndUrlSegments(curr, future) ||
|
|
||||||
!shallowEqual(curr.queryParams, future.queryParams);
|
|
||||||
|
|
||||||
case 'paramsChange':
|
|
||||||
default:
|
|
||||||
return !equalParamsAndUrlSegments(curr, future);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private deactivateRouteAndItsChildren(
|
|
||||||
route: TreeNode<ActivatedRouteSnapshot>, context: OutletContext|null): void {
|
|
||||||
const children = nodeChildrenAsMap(route);
|
|
||||||
const r = route.value;
|
|
||||||
|
|
||||||
forEach(children, (node: TreeNode<ActivatedRouteSnapshot>, childName: string) => {
|
|
||||||
if (!r.component) {
|
|
||||||
this.deactivateRouteAndItsChildren(node, context);
|
|
||||||
} else if (context) {
|
|
||||||
this.deactivateRouteAndItsChildren(node, context.children.getContext(childName));
|
|
||||||
} else {
|
|
||||||
this.deactivateRouteAndItsChildren(node, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!r.component) {
|
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
|
||||||
} else if (context && context.outlet && context.outlet.isActivated) {
|
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
|
|
||||||
} else {
|
|
||||||
this.canDeactivateChecks.push(new CanDeactivate(null, r));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanDeactivateChecks(): Observable<boolean> {
|
|
||||||
const checks$ = from(this.canDeactivateChecks);
|
|
||||||
const runningChecks$ = mergeMap.call(
|
|
||||||
checks$, (check: CanDeactivate) => this.runCanDeactivate(check.component, check.route));
|
|
||||||
return every.call(runningChecks$, (result: boolean) => result === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanActivateChecks(): Observable<boolean> {
|
|
||||||
const checks$ = from(this.canActivateChecks);
|
|
||||||
const runningChecks$ = concatMap.call(
|
|
||||||
checks$, (check: CanActivate) => andObservables(from(
|
|
||||||
[this.runCanActivateChild(check.path), this.runCanActivate(check.route)])));
|
|
||||||
return every.call(runningChecks$, (result: boolean) => result === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanActivate(future: ActivatedRouteSnapshot): Observable<boolean> {
|
|
||||||
const canActivate = future._routeConfig ? future._routeConfig.canActivate : null;
|
|
||||||
if (!canActivate || canActivate.length === 0) return of (true);
|
|
||||||
const obs = map.call(from(canActivate), (c: any) => {
|
|
||||||
const guard = this.getToken(c, future);
|
|
||||||
let observable: Observable<boolean>;
|
|
||||||
if (guard.canActivate) {
|
|
||||||
observable = wrapIntoObservable(guard.canActivate(future, this.future));
|
|
||||||
} else {
|
|
||||||
observable = wrapIntoObservable(guard(future, this.future));
|
|
||||||
}
|
|
||||||
return first.call(observable);
|
|
||||||
});
|
|
||||||
return andObservables(obs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanActivateChild(path: ActivatedRouteSnapshot[]): Observable<boolean> {
|
|
||||||
const future = path[path.length - 1];
|
|
||||||
|
|
||||||
const canActivateChildGuards = path.slice(0, path.length - 1)
|
|
||||||
.reverse()
|
|
||||||
.map(p => this.extractCanActivateChild(p))
|
|
||||||
.filter(_ => _ !== null);
|
|
||||||
|
|
||||||
return andObservables(map.call(from(canActivateChildGuards), (d: any) => {
|
|
||||||
const obs = map.call(from(d.guards), (c: any) => {
|
|
||||||
const guard = this.getToken(c, d.node);
|
|
||||||
let observable: Observable<boolean>;
|
|
||||||
if (guard.canActivateChild) {
|
|
||||||
observable = wrapIntoObservable(guard.canActivateChild(future, this.future));
|
|
||||||
} else {
|
|
||||||
observable = wrapIntoObservable(guard(future, this.future));
|
|
||||||
}
|
|
||||||
return first.call(observable);
|
|
||||||
});
|
|
||||||
return andObservables(obs);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractCanActivateChild(p: ActivatedRouteSnapshot):
|
|
||||||
{node: ActivatedRouteSnapshot, guards: any[]}|null {
|
|
||||||
const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null;
|
|
||||||
if (!canActivateChild || canActivateChild.length === 0) return null;
|
|
||||||
return {node: p, guards: canActivateChild};
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanDeactivate(component: Object|null, curr: ActivatedRouteSnapshot):
|
|
||||||
Observable<boolean> {
|
|
||||||
const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null;
|
|
||||||
if (!canDeactivate || canDeactivate.length === 0) return of (true);
|
|
||||||
const canDeactivate$ = mergeMap.call(from(canDeactivate), (c: any) => {
|
|
||||||
const guard = this.getToken(c, curr);
|
|
||||||
let observable: Observable<boolean>;
|
|
||||||
if (guard.canDeactivate) {
|
|
||||||
observable =
|
|
||||||
wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future));
|
|
||||||
} else {
|
|
||||||
observable = wrapIntoObservable(guard(component, curr, this.curr, this.future));
|
|
||||||
}
|
|
||||||
return first.call(observable);
|
|
||||||
});
|
|
||||||
return every.call(canDeactivate$, (result: any) => result === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private runResolve(future: ActivatedRouteSnapshot): Observable<any> {
|
|
||||||
const resolve = future._resolve;
|
|
||||||
return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
|
|
||||||
future._resolvedData = resolvedData;
|
|
||||||
future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve};
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
|
||||||
const keys = Object.keys(resolve);
|
|
||||||
if (keys.length === 0) {
|
|
||||||
return of ({});
|
|
||||||
}
|
|
||||||
if (keys.length === 1) {
|
|
||||||
const key = keys[0];
|
|
||||||
return map.call(
|
|
||||||
this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
|
|
||||||
}
|
|
||||||
const data: {[k: string]: any} = {};
|
|
||||||
const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
|
|
||||||
return map.call(this.getResolver(resolve[key], future), (value: any) => {
|
|
||||||
data[key] = value;
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return map.call(last.call(runningResolvers$), () => data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable<any> {
|
|
||||||
const resolver = this.getToken(injectionToken, future);
|
|
||||||
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
|
||||||
wrapIntoObservable(resolver(future, this.future));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
|
||||||
const config = closestLoadedConfig(snapshot);
|
|
||||||
const injector = config ? config.module.injector : this.moduleInjector;
|
|
||||||
return injector.get(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ActivateRoutes {
|
class ActivateRoutes {
|
||||||
constructor(
|
constructor(
|
||||||
private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState,
|
private routeReuseStrategy: RouteReuseStrategy, private futureState: RouterState,
|
||||||
private currState: RouterState) {}
|
private currState: RouterState, private forwardEvent: (evt: RouteEvent) => void) {}
|
||||||
|
|
||||||
activate(parentContexts: ChildrenOutletContexts): void {
|
activate(parentContexts: ChildrenOutletContexts): void {
|
||||||
const futureRoot = this.futureState._root;
|
const futureRoot = this.futureState._root;
|
||||||
|
@ -1145,6 +864,9 @@ class ActivateRoutes {
|
||||||
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
||||||
futureNode.children.forEach(
|
futureNode.children.forEach(
|
||||||
c => { this.activateRoutes(c, children[c.value.outlet], contexts); });
|
c => { this.activateRoutes(c, children[c.value.outlet], contexts); });
|
||||||
|
if (futureNode.children.length && futureNode.value.routeConfig) {
|
||||||
|
this.forwardEvent(new ChildActivationEnd(futureNode.value.routeConfig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private activateRoutes(
|
private activateRoutes(
|
||||||
|
@ -1220,28 +942,6 @@ function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfi
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null {
|
|
||||||
if (!snapshot) return null;
|
|
||||||
|
|
||||||
for (let s = snapshot.parent; s; s = s.parent) {
|
|
||||||
const route = s._routeConfig;
|
|
||||||
if (route && route._loadedConfig) return route._loadedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the list of T indexed by outlet name
|
|
||||||
function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) {
|
|
||||||
const map: {[outlet: string]: TreeNode<T>} = {};
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
node.children.forEach(child => map[child.value.outlet] = child);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateCommands(commands: string[]): void {
|
function validateCommands(commands: string[]): void {
|
||||||
for (let i = 0; i < commands.length; i++) {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
const cmd = commands[i];
|
const cmd = commands[i];
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {Route, UrlMatchResult} from './config';
|
import {Route, UrlMatchResult} from './config';
|
||||||
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||||
|
|
||||||
|
|
|
@ -41,14 +41,23 @@ export function shallowEqual(a: {[x: string]: any}, b: {[x: string]: any}): bool
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens single-level nested arrays.
|
||||||
|
*/
|
||||||
export function flatten<T>(arr: T[][]): T[] {
|
export function flatten<T>(arr: T[][]): T[] {
|
||||||
return Array.prototype.concat.apply([], arr);
|
return Array.prototype.concat.apply([], arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last element of an array.
|
||||||
|
*/
|
||||||
export function last<T>(a: T[]): T|null {
|
export function last<T>(a: T[]): T|null {
|
||||||
return a.length > 0 ? a[a.length - 1] : null;
|
return a.length > 0 ? a[a.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifys all booleans in an array are `true`.
|
||||||
|
*/
|
||||||
export function and(bools: boolean[]): boolean {
|
export function and(bools: boolean[]): boolean {
|
||||||
return !bools.some(v => !v);
|
return !bools.some(v => !v);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +94,10 @@ export function waitForMap<A, B>(
|
||||||
return map.call(last$, () => res);
|
return map.call(last$, () => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANDs Observables by merging all input observables, reducing to an Observable verifying all
|
||||||
|
* input Observables return `true`.
|
||||||
|
*/
|
||||||
export function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
export function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
||||||
const merged$ = mergeAll.call(observables);
|
const merged$ = mergeAll.call(observables);
|
||||||
return every.call(merged$, (result: any) => result === true);
|
return every.call(merged$, (result: any) => result === true);
|
||||||
|
|
|
@ -88,3 +88,14 @@ export class 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) {
|
||||||
|
const map: {[outlet: string]: TreeNode<T>} = {};
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node.children.forEach(child => map[child.value.outlet] = child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* @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 {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';
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
logs: string[] = [];
|
||||||
|
add(thing: string) { this.logs.push(thing); }
|
||||||
|
empty() { this.logs.length = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function provideTokenLogger(token: string, returnValue = true) {
|
||||||
|
return {
|
||||||
|
provide: token,
|
||||||
|
useFactory: (logger: Logger) => () => (logger.add(token), returnValue),
|
||||||
|
deps: [Logger]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export declare type ARSArgs = {
|
||||||
|
url?: UrlSegment[],
|
||||||
|
params?: Params,
|
||||||
|
queryParams?: Params,
|
||||||
|
fragment?: string,
|
||||||
|
data?: Data,
|
||||||
|
outlet?: string,
|
||||||
|
component: Type<any>| string | null,
|
||||||
|
routeConfig?: Route | null,
|
||||||
|
urlSegment?: UrlSegmentGroup,
|
||||||
|
lastPathIndex?: number,
|
||||||
|
resolve?: ResolveData
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createActivatedRouteSnapshot(args: ARSArgs): ActivatedRouteSnapshot {
|
||||||
|
return new ActivatedRouteSnapshot(
|
||||||
|
args.url || <any>[], args.params || {}, args.queryParams || <any>null,
|
||||||
|
args.fragment || <any>null, args.data || <any>null, args.outlet || <any>null,
|
||||||
|
<any>args.component, args.routeConfig || <any>{}, args.urlSegment || <any>null,
|
||||||
|
args.lastPathIndex || -1, args.resolve || {});
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, Component, Injectable, NgModule, NgModuleFactor
|
||||||
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteEvent, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
|
@ -428,7 +428,7 @@ describe('Integration', () => {
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const recordedEvents: any[] = [];
|
const recordedEvents: any[] = [];
|
||||||
router.events.forEach(e => recordedEvents.push(e));
|
router.events.forEach(e => e instanceof RouteEvent || recordedEvents.push(e));
|
||||||
|
|
||||||
router.navigateByUrl('/team/22/user/victor');
|
router.navigateByUrl('/team/22/user/victor');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
@ -986,7 +986,7 @@ describe('Integration', () => {
|
||||||
[{path: 'simple', component: SimpleCmp, resolve: {error: 'resolveError'}}]);
|
[{path: 'simple', component: SimpleCmp, resolve: {error: 'resolveError'}}]);
|
||||||
|
|
||||||
const recordedEvents: any[] = [];
|
const recordedEvents: any[] = [];
|
||||||
router.events.subscribe(e => recordedEvents.push(e));
|
router.events.subscribe(e => e instanceof RouteEvent || recordedEvents.push(e));
|
||||||
|
|
||||||
let e: any = null;
|
let e: any = null;
|
||||||
router.navigateByUrl('/simple') !.catch(error => e = error);
|
router.navigateByUrl('/simple') !.catch(error => e = error);
|
||||||
|
@ -2388,9 +2388,11 @@ describe('Integration', () => {
|
||||||
[RouteConfigLoadEnd],
|
[RouteConfigLoadEnd],
|
||||||
[RoutesRecognized, '/lazyTrue/loaded'],
|
[RoutesRecognized, '/lazyTrue/loaded'],
|
||||||
[GuardsCheckStart, '/lazyTrue/loaded'],
|
[GuardsCheckStart, '/lazyTrue/loaded'],
|
||||||
|
[ChildActivationStart],
|
||||||
[GuardsCheckEnd, '/lazyTrue/loaded'],
|
[GuardsCheckEnd, '/lazyTrue/loaded'],
|
||||||
[ResolveStart, '/lazyTrue/loaded'],
|
[ResolveStart, '/lazyTrue/loaded'],
|
||||||
[ResolveEnd, '/lazyTrue/loaded'],
|
[ResolveEnd, '/lazyTrue/loaded'],
|
||||||
|
[ChildActivationEnd],
|
||||||
[NavigationEnd, '/lazyTrue/loaded'],
|
[NavigationEnd, '/lazyTrue/loaded'],
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
@ -3342,7 +3344,7 @@ describe('Integration', () => {
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const events: any[] = [];
|
const events: any[] = [];
|
||||||
router.events.subscribe(e => events.push(e));
|
router.events.subscribe(e => e instanceof RouteEvent || events.push(e));
|
||||||
|
|
||||||
// supported URL
|
// supported URL
|
||||||
router.navigateByUrl('/include/user/kate');
|
router.navigateByUrl('/include/user/kate');
|
||||||
|
@ -3406,7 +3408,7 @@ describe('Integration', () => {
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const events: any[] = [];
|
const events: any[] = [];
|
||||||
router.events.subscribe(e => events.push(e));
|
router.events.subscribe(e => e instanceof RouteEvent || events.push(e));
|
||||||
|
|
||||||
location.go('/include/user/kate(aux:excluded)');
|
location.go('/include/user/kate(aux:excluded)');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
|
@ -10,13 +10,16 @@ import {Location} from '@angular/common';
|
||||||
import {TestBed, inject} from '@angular/core/testing';
|
import {TestBed, inject} from '@angular/core/testing';
|
||||||
|
|
||||||
import {ResolveData} from '../src/config';
|
import {ResolveData} from '../src/config';
|
||||||
import {PreActivation, Router} from '../src/router';
|
import {PreActivation} from '../src/pre_activation';
|
||||||
|
import {Router} from '../src/router';
|
||||||
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
||||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
import {ActivatedRouteSnapshot, RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
||||||
import {DefaultUrlSerializer} from '../src/url_tree';
|
import {DefaultUrlSerializer} from '../src/url_tree';
|
||||||
import {TreeNode} from '../src/utils/tree';
|
import {TreeNode} from '../src/utils/tree';
|
||||||
import {RouterTestingModule} from '../testing/src/router_testing_module';
|
import {RouterTestingModule} from '../testing/src/router_testing_module';
|
||||||
|
|
||||||
|
import {Logger, createActivatedRouteSnapshot, provideTokenLogger} from './helpers';
|
||||||
|
|
||||||
describe('Router', () => {
|
describe('Router', () => {
|
||||||
describe('resetRootComponentType', () => {
|
describe('resetRootComponentType', () => {
|
||||||
class NewRootComponent {}
|
class NewRootComponent {}
|
||||||
|
@ -56,12 +59,241 @@ describe('Router', () => {
|
||||||
const serializer = new DefaultUrlSerializer();
|
const serializer = new DefaultUrlSerializer();
|
||||||
const inj = {get: (token: any) => () => `${token}_value`};
|
const inj = {get: (token: any) => () => `${token}_value`};
|
||||||
let empty: RouterStateSnapshot;
|
let empty: RouterStateSnapshot;
|
||||||
|
let logger: Logger;
|
||||||
|
|
||||||
beforeEach(() => { empty = createEmptyStateSnapshot(serializer.parse('/'), null !); });
|
const CA_CHILD = 'canActivate_child';
|
||||||
|
const CA_CHILD_FALSE = 'canActivate_child_false';
|
||||||
|
const CAC_CHILD = 'canActivateChild_child';
|
||||||
|
const CAC_CHILD_FALSE = 'canActivateChild_child_false';
|
||||||
|
const CA_GRANDCHILD = 'canActivate_grandchild';
|
||||||
|
const CA_GRANDCHILD_FALSE = 'canActivate_grandchild_false';
|
||||||
|
const CDA_CHILD = 'canDeactivate_child';
|
||||||
|
const CDA_CHILD_FALSE = 'canDeactivate_child_false';
|
||||||
|
const CDA_GRANDCHILD = 'canDeactivate_grandchild';
|
||||||
|
const CDA_GRANDCHILD_FALSE = 'canDeactivate_grandchild_false';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
Logger, provideTokenLogger(CA_CHILD), provideTokenLogger(CA_CHILD_FALSE, false),
|
||||||
|
provideTokenLogger(CAC_CHILD), provideTokenLogger(CAC_CHILD_FALSE, false),
|
||||||
|
provideTokenLogger(CA_GRANDCHILD), provideTokenLogger(CA_GRANDCHILD_FALSE, false),
|
||||||
|
provideTokenLogger(CDA_CHILD), provideTokenLogger(CDA_CHILD_FALSE, false),
|
||||||
|
provideTokenLogger(CDA_GRANDCHILD), provideTokenLogger(CDA_GRANDCHILD_FALSE, false)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(inject([Logger], (_logger: Logger) => {
|
||||||
|
empty = createEmptyStateSnapshot(serializer.parse('/'), null !);
|
||||||
|
logger = _logger;
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('guards', () => {
|
||||||
|
it('should run CanActivate checks', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* \
|
||||||
|
* child (CA, CAC)
|
||||||
|
* \
|
||||||
|
* grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {
|
||||||
|
|
||||||
|
canActivate: [CA_CHILD],
|
||||||
|
canActivateChild: [CAC_CHILD]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, empty, TestBed, (result) => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run grandchild guards if child fails', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* \
|
||||||
|
* child (CA: x, CAC)
|
||||||
|
* \
|
||||||
|
* grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {canActivate: [CA_CHILD_FALSE], canActivateChild: [CAC_CHILD]}
|
||||||
|
});
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, empty, TestBed, (result) => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(logger.logs).toEqual([CA_CHILD_FALSE]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run grandchild guards if child canActivateChild fails', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* \
|
||||||
|
* child (CA, CAC: x)
|
||||||
|
* \
|
||||||
|
* grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD_FALSE]}
|
||||||
|
});
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, empty, TestBed, (result) => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD_FALSE]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run deactivate guards before activate guards', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* / \
|
||||||
|
* prev (CDA) child (CA)
|
||||||
|
* \
|
||||||
|
* grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const prevSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD]}});
|
||||||
|
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
||||||
|
});
|
||||||
|
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const currentState = new RouterStateSnapshot(
|
||||||
|
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
||||||
|
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
||||||
|
expect(logger.logs).toEqual([CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run activate if deactivate fails guards', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* / \
|
||||||
|
* prev (CDA) child (CA)
|
||||||
|
* \
|
||||||
|
* grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const prevSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_FALSE]}});
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
||||||
|
});
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const currentState = new RouterStateSnapshot(
|
||||||
|
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(logger.logs).toEqual([CDA_CHILD_FALSE]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should deactivate from bottom up, then activate top down', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* / \
|
||||||
|
* prevChild (CDA) child (CA)
|
||||||
|
* / \
|
||||||
|
* prevGrandchild(CDA) grandchild (CA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const prevChildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'prev_child', routeConfig: {canDeactivate: [CDA_CHILD]}});
|
||||||
|
const prevGrandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'prev_grandchild', routeConfig: {canDeactivate: [CDA_GRANDCHILD]}});
|
||||||
|
const childSnapshot = createActivatedRouteSnapshot({
|
||||||
|
component: 'child',
|
||||||
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
||||||
|
});
|
||||||
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
||||||
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
||||||
|
|
||||||
|
const currentState = new RouterStateSnapshot(
|
||||||
|
'prev', new TreeNode(empty.root, [
|
||||||
|
new TreeNode(prevChildSnapshot, [new TreeNode(prevGrandchildSnapshot, [])])
|
||||||
|
]));
|
||||||
|
|
||||||
|
const futureState = new RouterStateSnapshot(
|
||||||
|
'url',
|
||||||
|
new TreeNode(
|
||||||
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
||||||
|
|
||||||
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(logger.logs).toEqual([
|
||||||
|
CDA_GRANDCHILD, CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.empty();
|
||||||
|
checkGuards(currentState, futureState, TestBed, (result) => {
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(logger.logs).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolve', () => {
|
||||||
|
|
||||||
it('should resolve data', () => {
|
it('should resolve data', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* \
|
||||||
|
* a
|
||||||
|
*/
|
||||||
const r = {data: 'resolver'};
|
const r = {data: 'resolver'};
|
||||||
const n = createActivatedRouteSnapshot('a', {resolve: r});
|
const n = createActivatedRouteSnapshot({component: 'a', resolve: r});
|
||||||
const s = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n, [])]));
|
const s = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n, [])]));
|
||||||
|
|
||||||
checkResolveData(s, empty, inj, () => {
|
checkResolveData(s, empty, inj, () => {
|
||||||
|
@ -70,11 +302,18 @@ describe('Router', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wait for the parent resolve to complete', () => {
|
it('should wait for the parent resolve to complete', () => {
|
||||||
|
/**
|
||||||
|
* R --> R
|
||||||
|
* \
|
||||||
|
* null (resolve: parentResolve)
|
||||||
|
* \
|
||||||
|
* b (resolve: childResolve)
|
||||||
|
*/
|
||||||
const parentResolve = {data: 'resolver'};
|
const parentResolve = {data: 'resolver'};
|
||||||
const childResolve = {};
|
const childResolve = {};
|
||||||
|
|
||||||
const parent = createActivatedRouteSnapshot(null !, {resolve: parentResolve});
|
const parent = createActivatedRouteSnapshot({component: null !, resolve: parentResolve});
|
||||||
const child = createActivatedRouteSnapshot('b', {resolve: childResolve});
|
const child = createActivatedRouteSnapshot({component: 'b', resolve: childResolve});
|
||||||
|
|
||||||
const s = new RouterStateSnapshot(
|
const s = new RouterStateSnapshot(
|
||||||
'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));
|
'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));
|
||||||
|
@ -87,15 +326,22 @@ describe('Router', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should copy over data when creating a snapshot', () => {
|
it('should copy over data when creating a snapshot', () => {
|
||||||
|
/**
|
||||||
|
* R --> R --> R
|
||||||
|
* \ \
|
||||||
|
* n1 (resolve: r1) n21 (resolve: r1)
|
||||||
|
* \
|
||||||
|
* n22 (resolve: r2)
|
||||||
|
*/
|
||||||
const r1 = {data: 'resolver1'};
|
const r1 = {data: 'resolver1'};
|
||||||
const r2 = {data: 'resolver2'};
|
const r2 = {data: 'resolver2'};
|
||||||
|
|
||||||
const n1 = createActivatedRouteSnapshot('a', {resolve: r1});
|
const n1 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
|
||||||
const s1 = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
|
const s1 = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
|
||||||
checkResolveData(s1, empty, inj, () => {});
|
checkResolveData(s1, empty, inj, () => {});
|
||||||
|
|
||||||
const n21 = createActivatedRouteSnapshot('a', {resolve: r1});
|
const n21 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
|
||||||
const n22 = createActivatedRouteSnapshot('b', {resolve: r2});
|
const n22 = createActivatedRouteSnapshot({component: 'b', resolve: r2});
|
||||||
const s2 = new RouterStateSnapshot(
|
const s2 = new RouterStateSnapshot(
|
||||||
'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
|
'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
|
||||||
checkResolveData(s2, s1, inj, () => {
|
checkResolveData(s2, s1, inj, () => {
|
||||||
|
@ -104,17 +350,20 @@ describe('Router', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkResolveData(
|
function checkResolveData(
|
||||||
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
|
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
|
||||||
const p = new PreActivation(future, curr, injector);
|
const p = new PreActivation(future, curr, injector);
|
||||||
p.traverse(new ChildrenOutletContexts());
|
p.initalize(new ChildrenOutletContexts());
|
||||||
p.resolveData().subscribe(check, (e) => { throw e; });
|
p.resolveData().subscribe(check, (e) => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
||||||
function createActivatedRouteSnapshot(cmp: string, extra: any = {}): ActivatedRouteSnapshot {
|
function checkGuards(
|
||||||
return new ActivatedRouteSnapshot(
|
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
|
||||||
<any>[], {}, <any>null, <any>null, <any>null, <any>null, <any>cmp, <any>{}, <any>null, -1,
|
check: (result: boolean) => void): void {
|
||||||
extra.resolve);
|
const p = new PreActivation(future, curr, injector);
|
||||||
|
p.initalize(new ChildrenOutletContexts());
|
||||||
|
p.checkGuards().subscribe(check, (e) => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,16 @@ export interface CanLoad {
|
||||||
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class ChildActivationEnd extends RouteEvent {
|
||||||
|
toString(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class ChildActivationStart extends RouteEvent {
|
||||||
|
toString(): string;
|
||||||
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class ChildrenOutletContexts {
|
export declare class ChildrenOutletContexts {
|
||||||
getContext(childName: string): OutletContext | null;
|
getContext(childName: string): OutletContext | null;
|
||||||
|
@ -87,7 +97,7 @@ export declare class DefaultUrlSerializer implements UrlSerializer {
|
||||||
export declare type DetachedRouteHandle = {};
|
export declare type DetachedRouteHandle = {};
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized | RouteConfigLoadStart | RouteConfigLoadEnd | GuardsCheckStart | GuardsCheckEnd | ResolveStart | ResolveEnd;
|
export declare type Event = RouterEvent | RouteEvent;
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export interface ExtraOptions {
|
export interface ExtraOptions {
|
||||||
|
@ -99,11 +109,9 @@ export interface ExtraOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class GuardsCheckEnd {
|
export declare class GuardsCheckEnd extends RouterEvent {
|
||||||
id: number;
|
|
||||||
shouldActivate: boolean;
|
shouldActivate: boolean;
|
||||||
state: RouterStateSnapshot;
|
state: RouterStateSnapshot;
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -115,10 +123,8 @@ export declare class GuardsCheckEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class GuardsCheckStart {
|
export declare class GuardsCheckStart extends RouterEvent {
|
||||||
id: number;
|
|
||||||
state: RouterStateSnapshot;
|
state: RouterStateSnapshot;
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -135,10 +141,8 @@ export declare type LoadChildren = string | LoadChildrenCallback;
|
||||||
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
|
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationCancel {
|
export declare class NavigationCancel extends RouterEvent {
|
||||||
id: number;
|
|
||||||
reason: string;
|
reason: string;
|
||||||
url: string;
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -147,9 +151,7 @@ export declare class NavigationCancel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationEnd {
|
export declare class NavigationEnd extends RouterEvent {
|
||||||
id: number;
|
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -159,10 +161,8 @@ export declare class NavigationEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationError {
|
export declare class NavigationError extends RouterEvent {
|
||||||
error: any;
|
error: any;
|
||||||
id: number;
|
|
||||||
url: string;
|
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -183,12 +183,7 @@ export interface NavigationExtras {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationStart {
|
export declare class NavigationStart extends RouterEvent {
|
||||||
id: number;
|
|
||||||
url: string;
|
|
||||||
constructor(
|
|
||||||
id: number,
|
|
||||||
url: string);
|
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,10 +241,8 @@ export declare type ResolveData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class ResolveEnd {
|
export declare class ResolveEnd extends RouterEvent {
|
||||||
id: number;
|
|
||||||
state: RouterStateSnapshot;
|
state: RouterStateSnapshot;
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -260,10 +253,8 @@ export declare class ResolveEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class ResolveStart {
|
export declare class ResolveStart extends RouterEvent {
|
||||||
id: number;
|
|
||||||
state: RouterStateSnapshot;
|
state: RouterStateSnapshot;
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -293,19 +284,22 @@ export interface Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class RouteConfigLoadEnd {
|
export declare class RouteConfigLoadEnd extends RouteEvent {
|
||||||
route: Route;
|
|
||||||
constructor(route: Route);
|
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class RouteConfigLoadStart {
|
export declare class RouteConfigLoadStart extends RouteEvent {
|
||||||
route: Route;
|
|
||||||
constructor(route: Route);
|
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class RouteEvent {
|
||||||
|
route: Route;
|
||||||
|
constructor(
|
||||||
|
route: Route);
|
||||||
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class Router {
|
export declare class Router {
|
||||||
config: Routes;
|
config: Routes;
|
||||||
|
@ -453,10 +447,8 @@ export declare type Routes = Route[];
|
||||||
export declare const ROUTES: InjectionToken<Route[][]>;
|
export declare const ROUTES: InjectionToken<Route[][]>;
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class RoutesRecognized {
|
export declare class RoutesRecognized extends RouterEvent {
|
||||||
id: number;
|
|
||||||
state: RouterStateSnapshot;
|
state: RouterStateSnapshot;
|
||||||
url: string;
|
|
||||||
urlAfterRedirects: string;
|
urlAfterRedirects: string;
|
||||||
constructor(
|
constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
|
Loading…
Reference in New Issue