/
router_preloader.ts
159 lines (143 loc) Β· 5.69 KB
/
router_preloader.ts
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* @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.io/license
*/
import {Compiler, createEnvironmentInjector, EnvironmentInjector, Injectable, OnDestroy} from '@angular/core';
import {from, Observable, of, Subscription} from 'rxjs';
import {catchError, concatMap, filter, mergeAll, mergeMap} from 'rxjs/operators';
import {Event, NavigationEnd} from './events';
import {LoadedRouterConfig, Route, Routes} from './models';
import {Router} from './router';
import {RouterConfigLoader} from './router_config_loader';
/**
* @description
*
* Provides a preloading strategy.
*
* @publicApi
*/
export abstract class PreloadingStrategy {
abstract preload(route: Route, fn: () => Observable<any>): Observable<any>;
}
/**
* @description
*
* Provides a preloading strategy that preloads all modules as quickly as possible.
*
* ```
* RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules})
* ```
*
* @publicApi
*/
@Injectable({providedIn: 'root'})
export class PreloadAllModules implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
return fn().pipe(catchError(() => of(null)));
}
}
/**
* @description
*
* Provides a preloading strategy that does not preload any modules.
*
* This strategy is enabled by default.
*
* @publicApi
*/
@Injectable({providedIn: 'root'})
export class NoPreloading implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
return of(null);
}
}
/**
* The preloader optimistically loads all router configurations to
* make navigations into lazily-loaded sections of the application faster.
*
* The preloader runs in the background. When the router bootstraps, the preloader
* starts listening to all navigation events. After every such event, the preloader
* will check if any configurations can be loaded lazily.
*
* If a route is protected by `canLoad` guards, the preloaded will not load it.
*
* @publicApi
*/
@Injectable()
export class RouterPreloader implements OnDestroy {
private subscription?: Subscription;
constructor(
private router: Router, compiler: Compiler, private injector: EnvironmentInjector,
private preloadingStrategy: PreloadingStrategy, private loader: RouterConfigLoader) {}
setUpPreloading(): void {
this.subscription =
this.router.events
.pipe(filter((e: Event) => e instanceof NavigationEnd), concatMap(() => this.preload()))
.subscribe(() => {});
}
preload(): Observable<any> {
return this.processRoutes(this.injector, this.router.config);
}
/** @nodoc */
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private processRoutes(injector: EnvironmentInjector, routes: Routes): Observable<void> {
const res: Observable<any>[] = [];
for (const route of routes) {
if (route.providers && !route._injector) {
route._injector =
createEnvironmentInjector(route.providers, injector, `Route: ${route.path}`);
}
const injectorForCurrentRoute = route._injector ?? injector;
const injectorForChildren = route._loadedInjector ?? injectorForCurrentRoute;
// Note that `canLoad` is only checked as a condition that prevents `loadChildren` and not
// `loadComponent`. `canLoad` guards only block loading of child routes by design. This
// happens as a consequence of needing to descend into children for route matching immediately
// while component loading is deferred until route activation. Because `canLoad` guards can
// have side effects, we cannot execute them here so we instead skip preloading altogether
// when present. Lastly, it remains to be decided whether `canLoad` should behave this way
// at all. Code splitting and lazy loading is separate from client-side authorization checks
// and should not be used as a security measure to prevent loading of code.
if ((route.loadChildren && !route._loadedRoutes && route.canLoad === undefined) ||
(route.loadComponent && !route._loadedComponent)) {
res.push(this.preloadConfig(injectorForCurrentRoute, route));
} else if (route.children || route._loadedRoutes) {
res.push(this.processRoutes(injectorForChildren, (route.children ?? route._loadedRoutes)!));
}
}
return from(res).pipe(mergeAll());
}
private preloadConfig(injector: EnvironmentInjector, route: Route): Observable<void> {
return this.preloadingStrategy.preload(route, () => {
let loadedChildren$: Observable<LoadedRouterConfig|null>;
if (route.loadChildren && route.canLoad === undefined) {
loadedChildren$ = this.loader.loadChildren(injector, route);
} else {
loadedChildren$ = of(null);
}
const recursiveLoadChildren$ =
loadedChildren$.pipe(mergeMap((config: LoadedRouterConfig|null) => {
if (config === null) {
return of(void 0);
}
route._loadedRoutes = config.routes;
route._loadedInjector = config.injector;
// If the loaded config was a module, use that as the module/module injector going
// forward. Otherwise, continue using the current module/module injector.
return this.processRoutes(config.injector ?? injector, config.routes);
}));
if (route.loadComponent && !route._loadedComponent) {
const loadComponent$ = this.loader.loadComponent(route);
return from([recursiveLoadChildren$, loadComponent$]).pipe(mergeAll());
} else {
return recursiveLoadChildren$;
}
});
}
}