Repository navigation
Expand file tree
/
Copy pathresolve_data.ts
More file actions
140 lines (132 loc) Β· 4.9 KB
/
Copy pathresolve_data.ts
File metadata and controls
140 lines (132 loc) Β· 4.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {EnvironmentInjector, ProviderToken, runInInjectionContext} from '@angular/core';
import {defer, EMPTY, from, MonoTypeOperatorFunction, Observable, of, throwError} from 'rxjs';
import {catchError, concatMap, first, map, mergeMap, takeLast, tap} from 'rxjs/operators';
import {RedirectCommand, ResolveData} from '../models';
import type {NavigationTransition} from '../navigation_transition';
import {
ActivatedRouteSnapshot,
getInherited,
hasStaticTitle,
RouterStateSnapshot,
} from '../router_state';
import {RouteTitleKey} from '../shared';
import {getDataKeys, wrapIntoObservable} from '../utils/collection';
import {getTokenOrFunctionIdentity} from '../utils/preactivation';
import {isEmptyError} from '../utils/type_guards';
import {redirectingNavigationError} from '../navigation_canceling_error';
import {DefaultUrlSerializer} from '../url_tree';
export function resolveData(
paramsInheritanceStrategy: 'emptyOnly' | 'always',
): MonoTypeOperatorFunction<NavigationTransition> {
return mergeMap((t) => {
const {
targetSnapshot,
guards: {canActivateChecks},
} = t;
if (!canActivateChecks.length) {
return of(t);
}
// Iterating a Set in javascript happens in insertion order so it is safe to use a `Set` to
// preserve the correct order that the resolvers should run in.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#description
const routesWithResolversToRun = new Set(canActivateChecks.map((check) => check.route));
const routesNeedingDataUpdates = new Set<ActivatedRouteSnapshot>();
for (const route of routesWithResolversToRun) {
if (routesNeedingDataUpdates.has(route)) {
continue;
}
// All children under the route with a resolver to run need to recompute inherited data.
for (const newRoute of flattenRouteTree(route)) {
routesNeedingDataUpdates.add(newRoute);
}
}
let routesProcessed = 0;
return from(routesNeedingDataUpdates).pipe(
concatMap((route) => {
if (routesWithResolversToRun.has(route)) {
return runResolve(route, targetSnapshot!, paramsInheritanceStrategy);
} else {
route.data = getInherited(route, route.parent, paramsInheritanceStrategy).resolve;
return of(void 0);
}
}),
tap(() => routesProcessed++),
takeLast(1),
mergeMap((_) => (routesProcessed === routesNeedingDataUpdates.size ? of(t) : EMPTY)),
);
});
}
/**
* Returns the `ActivatedRouteSnapshot` tree as an array, using DFS to traverse the route tree.
*/
function flattenRouteTree(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot[] {
const descendants = route.children.map((child) => flattenRouteTree(child)).flat();
return [route, ...descendants];
}
function runResolve(
futureARS: ActivatedRouteSnapshot,
futureRSS: RouterStateSnapshot,
paramsInheritanceStrategy: 'emptyOnly' | 'always',
) {
const config = futureARS.routeConfig;
const resolve = futureARS._resolve;
if (config?.title !== undefined && !hasStaticTitle(config)) {
resolve[RouteTitleKey] = config.title;
}
return defer(() => {
futureARS.data = getInherited(futureARS, futureARS.parent, paramsInheritanceStrategy).resolve;
return resolveNode(resolve, futureARS, futureRSS).pipe(
map((resolvedData: any) => {
futureARS._resolvedData = resolvedData;
futureARS.data = {...futureARS.data, ...resolvedData};
return null;
}),
);
});
}
function resolveNode(
resolve: ResolveData,
futureARS: ActivatedRouteSnapshot,
futureRSS: RouterStateSnapshot,
): Observable<any> {
const keys = getDataKeys(resolve);
if (keys.length === 0) {
return of({});
}
const data: {[k: string | symbol]: any} = {};
return from(keys).pipe(
mergeMap((key) =>
getResolver(resolve[key], futureARS, futureRSS).pipe(
first(),
tap((value: any) => {
if (value instanceof RedirectCommand) {
throw redirectingNavigationError(new DefaultUrlSerializer(), value);
}
data[key] = value;
}),
),
),
takeLast(1),
map(() => data),
catchError((e: unknown) => (isEmptyError(e as Error) ? EMPTY : throwError(e))),
);
}
function getResolver(
injectionToken: ProviderToken<any> | Function,
futureARS: ActivatedRouteSnapshot,
futureRSS: RouterStateSnapshot,
): Observable<any> {
const closestInjector = futureARS._environmentInjector;
const resolver = getTokenOrFunctionIdentity(injectionToken, closestInjector);
const resolverValue = resolver.resolve
? resolver.resolve(futureARS, futureRSS)
: runInInjectionContext(closestInjector, () => resolver(futureARS, futureRSS));
return wrapIntoObservable(resolverValue);
}