Skip to content

Commit d8cf78b

Browse files
atscottAndrewKushnir
authored andcommitted
fix(router): Do not call preload method when not necessary (#47007)
In Angular 14, we introduced the `loadComponent` API for a `Route` to allow lazy loading of a routed component in addition to the existing `loadChildren` which allows lazy loading of child routes. As a result, the `preload` method of the `PreloadingStrategy` needs to sometimes be called even when there is a `canLoad` guard on the `Route`. `CanLoad` guards block loading of child routes but _do not_ block loading of the component. This change updates the conditional checks in the internal preloader to skip calling the `PreloadingStrategy.preload` when there is only a `loadChildren` callback with a `canLoad` guard an no `loadComponent`. In this case, the callback passed to the `preload` method is already effectively a no-op so it's not necessary to call it at all. resolves #47003 PR Close #47007
1 parent b757b13 commit d8cf78b

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

aio/content/guide/router-tutorial-toh.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,12 +2382,12 @@ Currently, the `AdminModule` does not preload because `CanLoad` is blocking it.
23822382

23832383
<a id="preload-canload"></a>
23842384

2385-
#### `CanLoad` blocks preload
2385+
#### `CanLoad` blocks preload of children
23862386

23872387
The `PreloadAllModules` strategy does not load feature areas protected by a [CanLoad](#can-load-guard) guard.
23882388

23892389
You added a `CanLoad` guard to the route in the `AdminModule` a few steps back to block loading of that module until the user is authorized.
2390-
That `CanLoad` guard takes precedence over the preload strategy.
2390+
That `CanLoad` guard takes precedence over the preload strategy for loading children routes.
23912391

23922392
If you want to preload a module as well as guard against unauthorized access, remove the `canLoad()` guard method and rely on the [canActivate()](#can-activate-guard) guard alone.
23932393

packages/router/src/router_preloader.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,15 @@ export class RouterPreloader implements OnDestroy {
110110
const injectorForCurrentRoute = route._injector ?? injector;
111111
const injectorForChildren = route._loadedInjector ?? injectorForCurrentRoute;
112112

113-
if ((route.loadChildren && !route._loadedRoutes) ||
113+
// Note that `canLoad` is only checked as a condition that prevents `loadChildren` and not
114+
// `loadComponent`. `canLoad` guards only block loading of child routes by design. This
115+
// happens as a consequence of needing to descend into children for route matching immediately
116+
// while component loading is deferred until route activation. Because `canLoad` guards can
117+
// have side effects, we cannot execute them here so we instead skip preloading altogether
118+
// when present. Lastly, it remains to be decided whether `canLoad` should behave this way
119+
// at all. Code splitting and lazy loading is separate from client-side authorization checks
120+
// and should not be used as a security measure to prevent loading of code.
121+
if ((route.loadChildren && !route._loadedRoutes && route.canLoad === undefined) ||
114122
(route.loadComponent && !route._loadedComponent)) {
115123
res.push(this.preloadConfig(injectorForCurrentRoute, route));
116124
} else if (route.children || route._loadedRoutes) {

packages/router/test/router_preloader.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('RouterPreloader', () => {
3939
});
4040
});
4141

42-
describe('should not load configurations with canLoad guard', () => {
42+
describe('configurations with canLoad guard', () => {
4343
@NgModule({
4444
declarations: [LazyLoadedCmp],
4545
imports: [RouterModule.forChild([{path: 'LoadedModule1', component: LazyLoadedCmp}])]
@@ -56,7 +56,7 @@ describe('RouterPreloader', () => {
5656
});
5757

5858

59-
it('should work',
59+
it('should not load children',
6060
fakeAsync(inject([RouterPreloader, Router], (preloader: RouterPreloader, router: Router) => {
6161
preloader.preload().subscribe(() => {});
6262

@@ -65,6 +65,17 @@ describe('RouterPreloader', () => {
6565
const c = router.config;
6666
expect((c[0] as any)._loadedConfig).not.toBeDefined();
6767
})));
68+
69+
it('should not call the preloading method because children will not be loaded anyways',
70+
fakeAsync(() => {
71+
const preloader = TestBed.inject(RouterPreloader);
72+
const preloadingStrategy = TestBed.inject(PreloadingStrategy);
73+
spyOn(preloadingStrategy, 'preload').and.callThrough();
74+
preloader.preload().subscribe(() => {});
75+
76+
tick();
77+
expect(preloadingStrategy.preload).not.toHaveBeenCalled();
78+
}));
6879
});
6980

7081
describe('should preload configurations', () => {

0 commit comments

Comments
 (0)