refactor(router): convert PreActivation's resolve data to pure functions & remove PreActivation class (#26239)
PR Close #26239
This commit is contained in:
parent
fe45b9cebd
commit
ac68c75e26
|
@ -6,19 +6,81 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MonoTypeOperatorFunction, Observable} from 'rxjs';
|
import {Injector} from '@angular/core';
|
||||||
|
import {MonoTypeOperatorFunction, Observable, from, of } from 'rxjs';
|
||||||
|
import {concatMap, last, map, mergeMap, reduce} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {ResolveData} from '../config';
|
||||||
import {NavigationTransition} from '../router';
|
import {NavigationTransition} from '../router';
|
||||||
import {switchTap} from './switch_tap';
|
import {ChildrenOutletContexts} from '../router_outlet_context';
|
||||||
|
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from '../router_state';
|
||||||
|
import {wrapIntoObservable} from '../utils/collection';
|
||||||
|
|
||||||
export function resolveData(paramsInheritanceStrategy: 'emptyOnly' | 'always'):
|
import {getAllRouteGuards, getToken} from './check_guards';
|
||||||
MonoTypeOperatorFunction<NavigationTransition> {
|
|
||||||
|
export function resolveData(
|
||||||
|
rootContexts: ChildrenOutletContexts, paramsInheritanceStrategy: 'emptyOnly' | 'always',
|
||||||
|
moduleInjector: Injector): MonoTypeOperatorFunction<NavigationTransition> {
|
||||||
return function(source: Observable<NavigationTransition>) {
|
return function(source: Observable<NavigationTransition>) {
|
||||||
return source.pipe(switchTap(t => {
|
return source.pipe(mergeMap(t => {
|
||||||
if (!t.preActivation) {
|
const {targetSnapshot, currentSnapshot} = t;
|
||||||
throw new Error('PreActivation required to resolve data');
|
const checks = getAllRouteGuards(targetSnapshot !, currentSnapshot, rootContexts);
|
||||||
|
|
||||||
|
if (!checks.canActivateChecks.length) {
|
||||||
|
return of (t);
|
||||||
}
|
}
|
||||||
return t.preActivation.resolveData(paramsInheritanceStrategy);
|
|
||||||
|
return from(checks.canActivateChecks)
|
||||||
|
.pipe(
|
||||||
|
concatMap(
|
||||||
|
check => runResolve(
|
||||||
|
check.route, targetSnapshot !, paramsInheritanceStrategy, moduleInjector)),
|
||||||
|
reduce((_: any, __: any) => _), map(_ => t));
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runResolve(
|
||||||
|
futureARS: ActivatedRouteSnapshot, futureRSS: RouterStateSnapshot,
|
||||||
|
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};
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNode(
|
||||||
|
resolve: ResolveData, futureARS: ActivatedRouteSnapshot, futureRSS: RouterStateSnapshot,
|
||||||
|
moduleInjector: Injector): Observable<any> {
|
||||||
|
const keys = Object.keys(resolve);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return of ({});
|
||||||
|
}
|
||||||
|
if (keys.length === 1) {
|
||||||
|
const key = keys[0];
|
||||||
|
return getResolver(resolve[key], futureARS, futureRSS, moduleInjector)
|
||||||
|
.pipe(map((value: any) => { return {[key]: value}; }));
|
||||||
|
}
|
||||||
|
const data: {[k: string]: any} = {};
|
||||||
|
const runningResolvers$ = from(keys).pipe(mergeMap((key: string) => {
|
||||||
|
return getResolver(resolve[key], futureARS, futureRSS, moduleInjector)
|
||||||
|
.pipe(map((value: any) => {
|
||||||
|
data[key] = value;
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
return runningResolvers$.pipe(last(), map(() => data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResolver(
|
||||||
|
injectionToken: any, futureARS: ActivatedRouteSnapshot, futureRSS: RouterStateSnapshot,
|
||||||
|
moduleInjector: Injector): Observable<any> {
|
||||||
|
const resolver = getToken(injectionToken, futureARS, moduleInjector);
|
||||||
|
return resolver.resolve ? wrapIntoObservable(resolver.resolve(futureARS, futureRSS)) :
|
||||||
|
wrapIntoObservable(resolver(futureARS, futureRSS));
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* @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, OperatorFunction} from 'rxjs';
|
|
||||||
import {map} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import {Event} from '../events';
|
|
||||||
import {PreActivation} from '../pre_activation';
|
|
||||||
import {ChildrenOutletContexts} from '../router_outlet_context';
|
|
||||||
import {RouterStateSnapshot} from '../router_state';
|
|
||||||
|
|
||||||
export const setupPreactivation =
|
|
||||||
(rootContexts: ChildrenOutletContexts, currentSnapshot: RouterStateSnapshot,
|
|
||||||
moduleInjector: Injector, forwardEvent?: (evt: Event) => void) =>
|
|
||||||
map((snapshot: RouterStateSnapshot) => {
|
|
||||||
const preActivation =
|
|
||||||
new PreActivation(snapshot, currentSnapshot, moduleInjector, forwardEvent);
|
|
||||||
preActivation.initialize(rootContexts);
|
|
||||||
return preActivation;
|
|
||||||
});
|
|
|
@ -1,357 +0,0 @@
|
||||||
/**
|
|
||||||
* @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, of } from 'rxjs';
|
|
||||||
import {concatMap, every, first, last, map, mergeMap, reduce} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config';
|
|
||||||
import {ActivationStart, ChildActivationStart, Event} 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 {
|
|
||||||
readonly route: ActivatedRouteSnapshot;
|
|
||||||
constructor(public path: ActivatedRouteSnapshot[]) {
|
|
||||||
this.route = 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: Event) => void) {}
|
|
||||||
|
|
||||||
initialize(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 canDeactivate$.pipe(mergeMap(
|
|
||||||
(canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveData(paramsInheritanceStrategy: 'emptyOnly'|'always'): Observable<any> {
|
|
||||||
if (!this.isActivating()) return of (null);
|
|
||||||
return from(this.canActivateChecks)
|
|
||||||
.pipe(
|
|
||||||
concatMap(
|
|
||||||
(check: CanActivate) => this.runResolve(check.route, paramsInheritanceStrategy)),
|
|
||||||
reduce((_: 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> {
|
|
||||||
return from(this.canDeactivateChecks)
|
|
||||||
.pipe(
|
|
||||||
mergeMap((check: CanDeactivate) => this.runCanDeactivate(check.component, check.route)),
|
|
||||||
every((result: boolean) => result === true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCanActivateChecks(): Observable<boolean> {
|
|
||||||
return from(this.canActivateChecks)
|
|
||||||
.pipe(
|
|
||||||
concatMap((check: CanActivate) => andObservables(from([
|
|
||||||
this.fireChildActivationStart(check.route.parent),
|
|
||||||
this.fireActivationStart(check.route), this.runCanActivateChild(check.path),
|
|
||||||
this.runCanActivate(check.route)
|
|
||||||
]))),
|
|
||||||
every((result: boolean) => result === true));
|
|
||||||
// this.fireChildActivationStart(check.path),
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
private fireActivationStart(snapshot: ActivatedRouteSnapshot|null): Observable<boolean> {
|
|
||||||
if (snapshot !== null && this.forwardEvent) {
|
|
||||||
this.forwardEvent(new ActivationStart(snapshot));
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
private fireChildActivationStart(snapshot: ActivatedRouteSnapshot|null): Observable<boolean> {
|
|
||||||
if (snapshot !== null && this.forwardEvent) {
|
|
||||||
this.forwardEvent(new ChildActivationStart(snapshot));
|
|
||||||
}
|
|
||||||
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 = from(canActivate).pipe(map((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 observable.pipe(first());
|
|
||||||
}));
|
|
||||||
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(from(canActivateChildGuards).pipe(map((d: any) => {
|
|
||||||
const obs = from(d.guards).pipe(map((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 observable.pipe(first());
|
|
||||||
}));
|
|
||||||
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$ = from(canDeactivate).pipe(mergeMap((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 observable.pipe(first());
|
|
||||||
}));
|
|
||||||
return canDeactivate$.pipe(every((result: any) => result === true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private runResolve(
|
|
||||||
future: ActivatedRouteSnapshot,
|
|
||||||
paramsInheritanceStrategy: 'emptyOnly'|'always'): Observable<any> {
|
|
||||||
const resolve = future._resolve;
|
|
||||||
return this.resolveNode(resolve, future).pipe(map((resolvedData: any): any => {
|
|
||||||
future._resolvedData = resolvedData;
|
|
||||||
future.data = {...future.data,
|
|
||||||
...inheritedParamsDataResolve(future, paramsInheritanceStrategy).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 this.getResolver(resolve[key], future).pipe(map((value: any) => {
|
|
||||||
return {[key]: value};
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
const data: {[k: string]: any} = {};
|
|
||||||
const runningResolvers$ = from(keys).pipe(mergeMap((key: string) => {
|
|
||||||
return this.getResolver(resolve[key], future).pipe(map((value: any) => {
|
|
||||||
data[key] = value;
|
|
||||||
return value;
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
return runningResolvers$.pipe(last(), map(() => 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;
|
|
||||||
}
|
|
|
@ -17,11 +17,10 @@ import {createUrlTree} from './create_url_tree';
|
||||||
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events';
|
||||||
import {activateRoutes} from './operators/activate_routes';
|
import {activateRoutes} from './operators/activate_routes';
|
||||||
import {applyRedirects} from './operators/apply_redirects';
|
import {applyRedirects} from './operators/apply_redirects';
|
||||||
import {checkGuards} from './operators/check_guards';
|
import {checkGuards, getAllRouteGuards} from './operators/check_guards';
|
||||||
import {recognize} from './operators/recognize';
|
import {recognize} from './operators/recognize';
|
||||||
import {resolveData} from './operators/resolve_data';
|
import {resolveData} from './operators/resolve_data';
|
||||||
import {switchTap} from './operators/switch_tap';
|
import {switchTap} from './operators/switch_tap';
|
||||||
import {PreActivation} from './pre_activation';
|
|
||||||
import {DefaultRouteReuseStrategy, RouteReuseStrategy} from './route_reuse_strategy';
|
import {DefaultRouteReuseStrategy, RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
import {RouterConfigLoader} from './router_config_loader';
|
import {RouterConfigLoader} from './router_config_loader';
|
||||||
import {ChildrenOutletContexts} from './router_outlet_context';
|
import {ChildrenOutletContexts} from './router_outlet_context';
|
||||||
|
@ -167,9 +166,6 @@ function defaultMalformedUriErrorHandler(
|
||||||
return urlSerializer.parse('/');
|
return urlSerializer.parse('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavStreamValue =
|
|
||||||
boolean | {appliedUrl: UrlTree, snapshot: RouterStateSnapshot, shouldActivate?: boolean};
|
|
||||||
|
|
||||||
export type NavigationTransition = {
|
export type NavigationTransition = {
|
||||||
id: number,
|
id: number,
|
||||||
currentUrlTree: UrlTree,
|
currentUrlTree: UrlTree,
|
||||||
|
@ -188,7 +184,6 @@ export type NavigationTransition = {
|
||||||
currentRouterState: RouterState,
|
currentRouterState: RouterState,
|
||||||
targetRouterState: RouterState | null,
|
targetRouterState: RouterState | null,
|
||||||
guardsResult: boolean | null,
|
guardsResult: boolean | null,
|
||||||
preActivation: PreActivation | null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -358,7 +353,6 @@ export class Router {
|
||||||
currentRouterState: this.routerState,
|
currentRouterState: this.routerState,
|
||||||
targetRouterState: null,
|
targetRouterState: null,
|
||||||
guardsResult: null,
|
guardsResult: null,
|
||||||
preActivation: null
|
|
||||||
});
|
});
|
||||||
this.navigations = this.setupNavigations(this.transitions);
|
this.navigations = this.setupNavigations(this.transitions);
|
||||||
|
|
||||||
|
@ -481,15 +475,10 @@ export class Router {
|
||||||
t.targetSnapshot !);
|
t.targetSnapshot !);
|
||||||
this.triggerEvent(guardsStart);
|
this.triggerEvent(guardsStart);
|
||||||
}),
|
}),
|
||||||
map(t => {
|
|
||||||
const preActivation = new PreActivation(
|
|
||||||
t.targetSnapshot !, t.currentSnapshot, this.ngModule.injector,
|
|
||||||
(evt: Event) => this.triggerEvent(evt));
|
|
||||||
preActivation.initialize(this.rootContexts);
|
|
||||||
return {...t, preActivation};
|
|
||||||
}),
|
|
||||||
|
|
||||||
checkGuards(this.rootContexts, this.ngModule.injector, (evt: Event) => this.triggerEvent(evt)),
|
checkGuards(
|
||||||
|
this.rootContexts, this.ngModule.injector,
|
||||||
|
(evt: Event) => this.triggerEvent(evt)),
|
||||||
|
|
||||||
tap(t => {
|
tap(t => {
|
||||||
const guardsEnd = new GuardsCheckEnd(
|
const guardsEnd = new GuardsCheckEnd(
|
||||||
|
@ -512,7 +501,8 @@ export class Router {
|
||||||
|
|
||||||
// --- RESOLVE ---
|
// --- RESOLVE ---
|
||||||
switchTap(t => {
|
switchTap(t => {
|
||||||
if (t.preActivation !.isActivating()) {
|
if (getAllRouteGuards(t.targetSnapshot !, t.currentSnapshot, this.rootContexts)
|
||||||
|
.canActivateChecks.length) {
|
||||||
return of (t).pipe(
|
return of (t).pipe(
|
||||||
tap(t => {
|
tap(t => {
|
||||||
const resolveStart = new ResolveStart(
|
const resolveStart = new ResolveStart(
|
||||||
|
@ -520,7 +510,9 @@ export class Router {
|
||||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !);
|
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !);
|
||||||
this.triggerEvent(resolveStart);
|
this.triggerEvent(resolveStart);
|
||||||
}),
|
}),
|
||||||
resolveData(this.paramsInheritanceStrategy), //
|
resolveData(
|
||||||
|
this.rootContexts, this.paramsInheritanceStrategy,
|
||||||
|
this.ngModule.injector), //
|
||||||
tap(t => {
|
tap(t => {
|
||||||
const resolveEnd = new ResolveEnd(
|
const resolveEnd = new ResolveEnd(
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
|
|
|
@ -13,8 +13,8 @@ import {of } from 'rxjs';
|
||||||
import {Routes} from '../src/config';
|
import {Routes} from '../src/config';
|
||||||
import {ChildActivationStart} from '../src/events';
|
import {ChildActivationStart} from '../src/events';
|
||||||
import {checkGuards as checkGuardsOperator} from '../src/operators/check_guards';
|
import {checkGuards as checkGuardsOperator} from '../src/operators/check_guards';
|
||||||
import {PreActivation} from '../src/pre_activation';
|
import {resolveData as resolveDataOperator} from '../src/operators/resolve_data';
|
||||||
import {Router} from '../src/router';
|
import {NavigationTransition, Router} from '../src/router';
|
||||||
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
||||||
import {RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
import {RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
||||||
import {DefaultUrlSerializer} from '../src/url_tree';
|
import {DefaultUrlSerializer} from '../src/url_tree';
|
||||||
|
@ -538,15 +538,15 @@ 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);
|
of ({ targetSnapshot: future, currentSnapshot: curr } as Partial<NavigationTransition>)
|
||||||
p.initialize(new ChildrenOutletContexts());
|
.pipe(resolveDataOperator(new ChildrenOutletContexts(), 'emptyOnly', injector))
|
||||||
p.resolveData('emptyOnly').subscribe(check, (e) => { throw e; });
|
.subscribe(check, (e) => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkGuards(
|
function checkGuards(
|
||||||
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
|
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
|
||||||
check: (result: boolean) => void): void {
|
check: (result: boolean) => void): void {
|
||||||
of ({ targetSnapshot: future, currentSnapshot: curr } as any)
|
of ({ targetSnapshot: future, currentSnapshot: curr } as Partial<NavigationTransition>)
|
||||||
.pipe(checkGuardsOperator(new ChildrenOutletContexts(), injector))
|
.pipe(checkGuardsOperator(new ChildrenOutletContexts(), injector))
|
||||||
.subscribe(t => check(!!t.guardsResult), (e) => { throw e; });
|
.subscribe(t => check(!!t.guardsResult), (e) => { throw e; });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue