Angular复习笔记7-路由(下)
这是angular路由的第二篇,也是最后一篇。继续上一章的内容
路由跳转
Web应用中的页面跳转,指的是应用响应某个事件,从一个页面跳转到另一个页面的行为。对于使用Angular构建的单页应用而言,页面跳转实质上就是从一个配置项跳转到另一个配置项的行为。页面跳转流程如下图所示,当某个事件引发了跳转时,Angular会根据跳转时的参数生成一个UrlTree实例来和配置项进行匹配,如果匹配成功,则显示相应的组件并将新URL更新在浏览器地址栏中;如果匹配不成功,则报错。本节将对Angular应用中进行页面跳转的两种方式进行介绍。
使用指令进行跳转
指令跳转通过使用RouterLink指令来完成。该指令接收一个链接参数数组,Angular将根据该数组来生成UrlTree实例进行跳转。
如果不借助于RouterLink指令而以纯HTML的方式来定义超链接,所导致的结果是单击超链接后会使得整个页面被重新加载。
RouteLink的一个强大之处在于可以用在任何的HTML元素上。使得页面跳转不需要超链接。
此外,当RouterLink被激活时,还可以通过RouterLinkActive指令为其相应的HTML元素指定CSS类。下面的例子定义了一个CSS类.active,并通过routerLinkActive将其赋给收藏页的链接。当单击该链接后,.active类将被应用到<a>标签上。示例代码如下:
RouterLinkActive指令除可以作用于routerLink所在的元素之外,还可以作用于这些元素的任意祖先元素。当该祖先元素下的任意routerLink处于激活状态时,该祖先元素都将获得routerLinkActive指定的CSS类。下面的例子不管当前是处于联系人列表页还是收藏页,<nav>标签都将获得.active类。示例代码如下:
使用代码跳转
RouterLink仅仅相应click事件,如果需要其他形式的跳转,在可以使用Router对象的Router.navigateByUrl()或其兄弟方法Router.navigate()来完成。下面的例子实现了在进入联系人列表页1秒后自动跳转到收藏页的功能。示例代码如下:
Router.navigateByUrl()和Router.navigate()的不同之处在于传入的参数不同,前者需要传入一个表示url的字符串或UrlTree类型的参数,后者和RouterLink指令一样,需要一个链接参数数组。
这两个方法除可以通过第一个参数来指定目标配置项外,还支持用extras参数定义跳转的具体行为。例如,如果想在不改变URL的情况下完成跳转,则可以通过以下代码来完成:
关于extras参数的其他用法,感兴趣的读者可以参考官方文档来了解更多的内容,在此不再赘述。
路由参数
在“组件”章节中介绍了如何使用@Input装饰器向组件传递数据,除此之外,Angular路由还提供了路由参数的功能,允许通过URL向组件传递数据。
path参数
顾名思义,Path参数是通过解析URL的path部分来获取参数的。在定义一个配置项的path属性时,可以使用“/”字符来对path属性进行分段,如果一个分段以“:”字符开头,则URL中与该分段进行匹配的部分将作为参数传递到组件中。下面的代码为联系人详情页的路由配置项,其定义了一个名为id的Path参数,对于http://localhost:3000/detail/1,参数id的值为1;对于http://localhost:3000/detail/2,参数id的值则为2;依此类推。
本例中path的分段数是2,只有URL解析出来的分段数和path的分段数一致时,才能得到匹配。
给路由参数赋值,除可以直接在浏览器地址栏中输入URL外,还可以通过RouterLink指令或者跳转方法来完成:
在组件中获取Path参数,需要导入ActivatedRoute服务,该服务提供了两种方式,分别适用于不同页面间跳转和同一页面内跳转。
Angular应用从一个页面跳转到另一个新的页面,实质上是从一个配置项跳转到另一个配置项。在这个过程中,Angular除会为配置项所对应的组件创建实例外,还会为该配置项本身创建一个ActivatedRoute实例来表示该配置项已被激活。该ActivatedRoute实例包含了一个快照(即snapshot属性),记录了从当前URL中解析出来的所有Path参数。下面展示了通讯录例子中的DetailComponent组件是如何通过快照来获取Path参数的。示例代码如下:
此时通过http://localhost:3000/detail/1直接访问联系人详情页,可以在浏览器控制台上看到如下输出,则表示通过快照获取到的值是正确的。
创建DetailComponent组件实例
参数id的值为:1
但是当Angular在处理同一页面内跳转时,不会重新创建组件的实例,所以组件的构造函数和ngOnInit()方法都没有被调用到。为了解决这个问题,ActivatedRoute服务提供了一个Observable对象,允许对参数的更新进行订阅。示例代码如下:
Query参数
我们也可以通过解析URL的query部分来获取参数值。由于URL的query部分不用于和配置项进行匹配,因此每一个配置项都可以拥有任意多个查询参数。下面的URL给联系人列表页定义了一个查询参数,表示只希望在页面上显示5位联系人。
http://localhost:3000/list?limit=5
与Path参数类似,Query参数同样可以通过RouterLink指令或者跳转方法来赋值。示例代码如下:
Query参数的获取,需要借助于ActivatedRoute服务提供的Observable类型对象queryParams来完成。下面的代码片段展示了如何使用这个对象。
Matrix参数
页面上所有组件都可以访问Query参数的内容,如果想精准地向某一个组件传递参数,则需要使用Matrix参数。
Angular提供了Matrix参数,它通过在链接参数数组中插入一个对象来进行赋值。示例代码如下:
Angular会将该对象的属性转化为以“;”为分隔符的键值对,拼接到与该对象左边最近的URL分段上。依据上述链接参数数组生成的URL如下,DetailComponent组件和AlbumComponent组件都将获得不同的参数值:http://localhost:3000/detail/6;after=2015-01-01;before=2015-12-31/album;after=2016-01-01;before=2016-12-31
这种在一个URL分段内使用“;”分隔键值对的方式称为MatrixURI,由互联网之父TimBerners-Lee于1996年提出。根据其定义,每一个URL分段都可以拥有任意多个键值对,每个键值对只为其所在的分段服务。虽然MatrixURI一直没有进入HTML标准,但它能够清晰地表示出每一个URL分段所具有的键值对。Angular利用这个特性,将Matrix参数精准地传递给分段所对应的组件。Matrix参数的获取方式和Path参数一样,可以通过ActivatedRoute服务提供的快照和Observable对象两种方式来获取,在此不再赘述。
路由拦截
Angular的路由拦截允许在从一个配置项跳转到另一个配置项之前执行指定的逻辑,并根据执行的结果来决定是否进行跳转。Angular提供了五类路由拦截:
- CanActivate,激活拦截。
- CanActivateChild,与CanActivate类似,用于控制是否允许激活子路由配置项。
- CanDeactivate,反激活拦截。
- Resolve,数据预加载拦截。
- CanLoad,模块加载拦截。
关于路由拦截的内容会新开一个专题篇来讲述这个功能的方方面面,这里先不赘述。
模块的延迟加载
前文提到,Angular应用由一个根模块和任意多个特性模块组成。一个大型Web应用通常会包含为数不少的特性模块,如果在首屏加载时便将所有的特性模块加载进来,对于用户体验和服务器负载均会有所影响。为此,Angular路由提供了对特性模块进行延迟加载的支持,使得只有在真正需要某一个模块的时候,才将其加载进来。
与根模块需要初始化各项路由服务不同,特性模块仅需要对其路由配置进行解析,因此子路由模块通过调用RouterModule.forChild()方法来创建。示例代码如下:
最后,还需要对根模块的路由配置进行修改:
loadChildren指定了延迟加载模块的路径,井号“#”后面的表示模块类名。当用户访问地址/operate时,Angular才会加载operate.module.ts这个模块。
模块预加载
延迟加载使得首屏加载的资源包的大小减小很多,这些模块只在用户触发的时候才开始加载。但对于某些模块来说,触发时才加载可能不是最优的解决方案。这样的模块虽然不需要首屏加载,但可能有很大的概率用户会访问使用到,因此最好不用等待用户触发,而是在首屏资源加载完后立即加载,这种加载模式就叫作预加载。预加载的模块首先得是一个延迟加载的模块,让所有延迟加载的模块加上预加载功能非常简单,只需在根模块的RouterModule中添加一个preloadingStrategy配置项即可。示例代码如下:
加上这个配置后,所有的延迟加载模块将不再等待用户触发,而是等待首屏资源加载完后立即加载。不过,这样的配置显然不够灵活,更好的方式是对预加载的策略做自定义配置。开发者可以通过实现Angular提供的PreloadingStrategy接口自定义预加载策略。首先定义一个服务,并实现PreloadingStrategy接口。示例代码如下:
preload()方法的返回类型必须是一个Observable对象,Angular会遍历每一个route对象并执行preload()函数,以此来判断该route对应的模块是否需要进行预加载。它接受两个参数:
route:当前处理中的route对象。
load:内置异步模块加载器函数。上面这个例子直接返回Observable.of(null),表示不进行预加载。
MyPreloadingStrategy这个服务的目的是进行有选择的预加载,可以根据route对象里的data属性提供的信息进行判断。示例代码如下:
如果data对象里设置了preload为true,preload函数即返回load()加载器函数,这表示该路由对应的模块需要进行预加载。这个MyPreloadingStrategy服务已经完成了,下面需要把原来的PreloadAllModules替换成新的MyPreloadingStrategy。示例代码如下:
然后依据这个规则,控制模块预加载就变得非常简单了。在需要预加载的延迟加载路由配置项里进行配置:
在这个route对象里设置preload为true后,OperateModule的加载方式由原来的延迟加载变更为预加载,而其他延迟加载模块并不会受到影响,还是会等待用户触发时才加载。