懒加载路由
顾名思义,懒加载就是最初不加载,在需要的时候才加载组件。
我们将改造前面的 admin 组件为懒加载模式。实现方式也很简单,只需要简单的三步:
1. 将原来的 admin-routing 中 admin 配置的 path 设为空(''):
// admin-routing.module.ts
{
path: '',
component: AdminComponent,
...
}
2. 在引入 admin 模块的任意父级模块的路由配置文件中配置懒加载:
// app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./components/router-study/admin/admin.module').then(m => m.AdminModule),
},
...
];
3. 在原来引入 AdminModule 的模块中删除:

admin 组件的懒加载,只有进入模块才加载对应的资源:
CanLoad 守卫
前面我们已经使用 CanActivate 守卫来拦截未登录的用户进入 admin 模块,并且也添加了路由懒加载来动态添加组件。但是,如果用户没有登录,想要进入 admin 模块,程序还是会加载 AdminModule。最理想的情况应该是这样的:用户只有在已经登录的情况下进入 admin 模块时才加载 AdminModule。
顾名思义, CanLoad 守卫就是能不能加载的意思,所以,我们用它来实现上面的需求。
auth.guard ,我们来实现 CanLoad 守卫:// auth.guard.ts
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
// ...
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const url = \`/\${route.path}\`;
return this.checkLogin(url);
}
private checkLogin(url: string): true | UrlTree {
// 已经登录,直接返回true
if (this.authService.isLoggedIn) { return true; }
// 修改登陆后重定向的地址
this.authService.redirectUrl = url;
// 重定向到登录页面
return this.router.parseUrl('/login');
}
}
// app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./components/router-study/admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}
// ...
];
admin 模块是不会加载 AdminModule ,只有登录了才加载:
预加载路由
预加载是指在程序加载完必要资源后,空闲时段预先加载其他资源。懒加载路由一般用于使用频率较低的组件,而预加载则用于使用频率较高的组件。
angular 本身提供了两种预加载策略:完全不预加载,这是默认值,以及预加载所有异步路由。当然,也支持自定义预加载策略。
comment 组件没有成为一个单独的模块,为了后面的演示,我们需要把它分成单独的一个模块。ng g m components/router-study/comment
comment 的配置放在 comment.mudule 里:// comment.mudule.ts
...
@NgModule({
declarations: [CommentsComponent, CommentComponent],
imports: [CommonModule],
providers: [CommentService]
})
...
comment 就抽离成一个单独的模块了。同样的将 user 也抽离成一个单独的模块,就不演示了。按照前面的三步将 comment 模块配置成异步加载形式:// app-routing.module.ts
const routes: Routes = [
{
path: 'comments',
loadChildren: () => import('./components/router-study/comment/comment.module').then(m => m.CommentModule)
},
// ...
];
将程序设置成预加载所有异步路由策略。
RouterModule.forRoot() 方法的第二个参数接受一个附加配置选项对象。 把 PreloadAllModules 添加到 forRoot() 调用中:
// app-routing.module.ts
@NgModule({
imports: [RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules})],
...
})
我们将浏览器的调试工具设置成 'Slow 3G' 网速,以便能够看到明显的效果:

可以看出,当我们加载完当前页面必需的资源后,最后加载了 CommentModule 。但是,为什么没有加载同样是异步加载的 AdminModule 呢?因为 CanLoad 守卫会阻塞预加载, CanLoad 守卫的优先级高于预加载策略。
自定义预加载策略
预加载全部的异步路由未免太过于激进了,按照我们的意愿来进行预加载才是正确的解决方式。我们可以在路由配置中设定一个 preload 标识符来判定路由是否需要预加载。
在全局生成一个新的 SelectivePreloadingStrategy 服务:
ng g s services/SelectivePreloadingStrategy
在生成的文件需要实现 PreloadingStrategy 接口以及对应的 preload() 方法:
// selective-preloading-strategy.service.ts
export class SelectivePreloadingStrategyService implements PreloadingStrategy {
// 记录预加载的模块
preloadedModules: string[] = [];
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data.preload) { // 使用预加载的条件
this.preloadedModules.push(route.path);
// 最重要的就是返回这个方法
return load();
} else {
return of(null);
}
}
}
修改预加载策略为我们自定义的这个:
// app-routing.module.ts
@NgModule({
imports: [RouterModule.forRoot(routes, {preloadingStrategy: SelectivePreloadingStrategyService})],
...
})
给我们需要的预加载的路由配置 preload 标识符:
// app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./components/router-study/admin/admin.module').then(m => m.AdminModule),
// canLoad: [AuthGuard],
data: {preload: true}
}
// ...
];
注意: 当我将 comment 路由也添加预加载后,发现程序会一直重复执行自定义策略里面的 load() 方法,导致程序出错。具体原因目前未知,等找到解决方法后再进行告知。
所以,我将默认路由改成了 comment 路由,对 user 路由进行预加载,程序运行正常。

如果你想在程序中获取有哪些路由启用预加载,也可以通过服务的方式获取到。
我们将 SelectivePreloadingStrategyService 服务注入到 AppRoutingModule 的 providers 数组中,以便它可以注入到应用中的任何地方:
// app-routing.module.ts
@NgModule({
providers: [SelectivePreloadingStrategyService],
...
})
在任意组件中获取:
export class AdminDashboardComponent implements OnInit {
constructor(private preloadStrategy: SelectivePreloadingStrategyService) { }
ngOnInit(): void {
console.log(this.preloadStrategy.preloadedModules); // ["admin", "users"]
}
}
并且会在任意地方打印出日志,不管你有没有进入到调用打印的组件里!
路由事件
Router 在每次导航过程中都会通过 Router.events 属性发出导航事件。这些事件的范围贯穿从导航开始和结束之间的多个时间点。导航事件的完整列表如下图所示:
constructor(private router: Router) {
this.router.events
.pipe(filter(events => events instanceof NavigationStart))
.subscribe((event: NavigationStart) => console.log(event));
}
总结
-
修改原路由配置文件 path为空; -
删除原加载模块的引用; -
父级或顶层模块动态引入。
CanLoad 守卫用于拦截异步路由,优先级高于预加载策略;


最新评论