Angular 路由学习小结
路由的概念
什么是路由
路由对于任何前端框架或库来说都是必不可少的。只加载一次应用,即可通过客户端路由向用户显示不同的内容,从而使单页应用成为可能。构建单页应用。这意味着当 URL 改变时,我们实际上并不会从服务器加载新页面。相反,路由器在浏览器中提供基于位置的导航。它允许我们在不刷新页面的情况下改变用户看到的内容以及 URL。
路由的用法
从一个简单的例子开始
- 当使用"ng new [app name]"命令创建一个Angular应用程序时,可以指定该程序是否是带路由的。带路由的应用会存在一个名为"app-routing.module.ts"的文件。
该文件内容如下所示:
import { NgModule} from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// 路由配置数组
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
// 路由组件
export class AppRoutingModule { }
imports选项中的"RouterModule.forRoot(…)"负责注册组件和全局配置。
- 在路由配置数组中定义路由
数组中的每个元素都是JavaScript对象。例如下面的定义,每个元素都包含了两个属性 path 和 component 。path 属性定义了该路由的 URL 路径,而component 属性定义了该路径应该渲染哪个组件。
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
];
- 在模板文件中添加路由。例如在根组件的模板中添加如下路由,并添加指令router-outlet,router-outlet是一个占位符,Angular 会根据当前的路由器状态动态填充它(准确说不是填充,而是将导航到的新组件渲染成它的兄弟节点)。
<!--省略上文-->
<ul>
<li><a routerLink="first" routerLinkActive="active">First Component</a></li>
<li><a routerLink="second" routerLinkActive="active">Second Component</a></li>
</ul>
<router-outlet></router-outlet>
关于路由配置数组Routes
-
路由顺序
路由的顺序很重要,因为 Router 在匹配路由时使用“先到先得”策略,所以应该在不那么具体的路由前面放置更具体的路由。
通常,路由配置数组的书写顺序是:
(1)静态路由,
(2)与默认路由匹配的空路径路由,
(3)通配符路由,因为它匹配每一个 URL,只有当其它路由都没有匹配时,Router 才会选择它。 -
通配符路由
通配符路由常见的一个应用场景就是,当用户导航到一个不存在的组件时,需要向用户展示"Page Not Found"页面。此时,可以设置通配符路由,当所请求的 URL 与任何其他路由器路径都不匹配时,就会匹配到通配符路由,从而向用户展示404页面。 -
重定向路由
要设置重定向,请使用重定向源的 path、要重定向目标的 component 和一个 pathMatch 值来配置路由,以告诉路由器该如何匹配 URL。
在Routes数组中添加一个元素如下:
{path: '', redirectTo: 'first', pathMatch: 'prefix'}
在浏览器上输入"http://localhost:4200/“,(4200是Angular服务端口号),再键Enter,会发现地址栏马上变成"http://localhost:4200/first”,即重定向到了"first"路径下。
重定向路由中,pathMatch有两个值可选,“prefix” 和 “full” :
(1)对于prefix,如果重定向源的 path是当前路径的前缀,则匹配成功。注意,这里的前缀不是以字符为单位进行匹配的,而是路径段。例如path = ‘a’,而当前路径是 ‘/abc’ , 则不会成功匹配。但 ‘/a/b’ 是可以成功匹配的。
(2)对于full,只有当前路径和path一样时,才能匹配成功。
- 子路由/嵌套路由
随着应用变得越来越复杂,可能要创建一些相对路由。这些嵌套路由类型称为子路由。
如果一个组件要包含子路由的话,它通常要涉及两个地方的配置。
(1)第一个就是该组件的模板,在该组件的模板中添加<router-outlet></router-outlet>
, 子组件将填充到模板中成为<router-outlet>
的兄弟节点。
(2)配置路由数组Routes,在该组件对应的元素中,添加children数组,children数组中存放子路由项。如下所示,
const routes: Routes = [
// first-component 及其子组件的路由项
{
path: 'first-component',
component: FirstComponent, // this is the component with the <router-outlet> in the template
children: [
{
path: 'child-a', // child route path
component: ChildAComponent, // child route component that the router renders
},
{
path: 'child-b',
component: ChildBComponent, // another child route component that the router renders
},
],
},
// .....其他路由项
]
关于模板文件a标签上的routeLink
模板文件中的 routeLink 与 href 的之间主要的区别是:href会触发页面重载,而routeLink会告诉路由器更新 URL 并使用 <router-outlet>
指令渲染内容,而无需重载页面。
路由导航的生命周期
查看导航循环的一个好办法是订阅路由器服务的 events
observable,控制台上可以看到导航生命周期。
constructor(private router: Router) {
this.route.events.subscribe((event) => {
console.log(event)
});
}
每当路由器检测到对路由器链接指令的点击时,它就会启动导航循环。启动导航也有其他的方式,例如路由服务的 navigate
和 navigateByUrl
方法。路由导航会经过下图中的几个过程:
(1)应用URL重定向确定最终的URL
(2)将URL与路由树进行匹配
(3)经过路由守卫与数据解析
(4)激活组件,挂载到router-outlet挂载点
(5)导航并更新浏览器的定位
接下来将会一一详细介绍这一过程:
URL匹配与重定向——路由应该导航到哪个组件
首先,路由器会对路由器配置数组(我们的示例中的 ROUTES
)进行深度优先搜索,并尝试将 URL 和众多路由配置项的 path
属性相匹配,同时在此过程中应用重定向。当路由器找到与URL匹配的路由项以及一个将要导航到的组件时,它发出 RoutesRecognized
事件(UsersComponent)。
路由守卫(Route Guards)——确定是否可以执行导航操作
路由守卫是布尔函数,路由器使用它来确定是否可以执行导航操作。作为开发人员,我们使用守卫(guards)来控制导航是否可以发生。例如,通过在路由配置中指定 canActivate 守卫来检查用户的登陆状态。
在路由配置数组的路由项配置canActivate属性,canActivate属性是一个any[]类型的,CanActivateGuard是守卫函数的名称。
// 路由配置数组中:
{ path: 'users', ..., canActivate: [CanActivateGuard] }
使用"ng generate guard [守卫函数的名称]" 来创建一个名为CanActivateGuard守卫函数,如下所示
import { CanActivateFn } from '@angular/router';
export const CanActivateGuard: CanActivateFn = (route, state) => {
// .....业务操作
return true;// 继续导航
// return false; 取消导航
};
若守卫函数返回true,则继续导航。若守卫函数返回false,则路由器会发出NavigationCancel
事件,然后中止导航。
其他的守卫(guards)包括 canLoad(模块是否被懒加载)、canActivateChild 和 canDeactivate(在存在表单填写的场景下,为了防止已填写的表单信息丢失,阻止用户直接从当前页面导航离开)。
路由解析器(Route Resolvers)——组件数据预取
路由解析器(Route Resolvers)可以用来提前准备组件所需要的数据,是在路由器渲染内容之前用来预取数据的函数。
路由解析器的使用:
(1)在路由配置中使用 resolve 属性指定解析器,例如下面的代码片段,名为users的数据指定给UserResolver解析。
{ path: 'users', ..., resolve: { users: UserResolver } }
(2)实现数据解析器中的resolve方法
// user.resolver.ts
export class UserResolver implements Resolve<Observable<any>> {
constructor(private userService: MockUserDataService) {}
resolve(): Observable<any> {
return this.userService.getUsers();
}
}
(3)在组件的ngOnInit函数中,通过ActivatedRoute从解析器中检索数据。
export class UsersComponent implements OnInit {
public users = [];
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.data.subscribe(data => this.users = data.users);
}
}
激活路由
激活路由就是激活路由对应的组件,并将它们渲染到<router-outlet>
指定的位置。过程大致可以分为三个步骤:
(1)从ActivatedRouteSnapshot 中提取激活的组件的信息,并创建组件实例。
(2)渲染的组件内容被放置为 <router-outlet>
的同级内容。注意,这里不是放置到 <router-outlet>
内部。
(3)如果存在激活的子路由,则继续处理子路由。
更新URL
将URL更新成Final URL
// router_link.ts
private updateTargetUrlAndHref(): void {
this.href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.urlTree));
}
其他
ActivatedRouteSnapshot 和 ActivatedRoute 的区别:https://www.coder.work/article/1177189
RouterState and RouterStateSnapshot:https://blog.csdn.net/railsbug/article/details/78051152
参考
[1] https://juejin.cn/post/6844904053269331982
[2] https://www.w3cschool.cn/angular13/angular13-lg463p03.html
[3] 《Angular 权威教程》
[4] forRoot和forChild:https://www.zhihu.com/question/293728198?utm_id=0
[5] Angular 路由生命周期:https://zhuanlan.zhihu.com/p/55212010?utm_id=0
[6] https://github.com/AngularID-CN/AngularID-CN