懒加载路由
顾名思义,懒加载就是最初不加载,在需要的时候才加载组件。
我们将改造前面的 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
守卫用于拦截异步路由,优先级高于预加载策略;
最新评论