本文讲解一下如何在Angular项目中使用router。
路由配置(NgModule based apps)
创建一个Angular项目
| 1 | ng new angular-router | 
在创建过程中,Angular CLI会询问是否使用router,选择Yes。
创建三个组件。
使用WebStorm或者VSCode打开上面创建的项目,呼出Terminal,执行以下命令。
| 1 | ng generate component home | 
然后打开app-routing.module.ts文件,创建路由。注意,path不能以/开头。
| 1 | // app-routing.module.ts | 
添加导航链接
打开app.component.html文件,添加导航链接。注意,routerLink的值要和path一致。如果是根路由,可以使用/。文档末尾的router-outlet是用来显示路由对应的组件的。当点击某一个链接的时候,Angular会找到对应的组件,并将其加载到router-outlet中。所以router-outlet相当于一个占位符,与html中的slot类似。
| 1 | <h1>Angular Router Demo</h1> | 
运行项目
在Terminal中执行以下命令。打开浏览器,输入http://localhost:4200,就可以看到导航链接了。依次点击页面上的Home,Product,About链接,可以看到页面跳转了,并且浏览器中的地址也发生了变化。
| 1 | ng serve | 
基于ROUTES的路由配置
ROUTES是Angular内置的一个Injection Token,在一些比较老的Angular app中,也有使用ROUTES来配置路由的,现举例如下:
| 1 | ({ | 
useFactory后面跟的是一个工厂函数,这个函数我们直接返回一个路由数组。
使用这种方法后,我们就不需要路由文件app-routing.module.ts了,可以将它删除掉。
路由配置(Standalone apps)
基于standalone组件的app,路由配置稍有不同。
- 在main.ts文件中启动app时会传递appConfig进去,我们的路由配置就在这个appConfig中。
| 1 | // main.ts | 
- 在app.config.ts文件中通过provideRouter函数提供路由。
| 1 | // app.config.ts | 
- 在app.routes.ts文件中定义路由。
| 1 | // app.routes.ts | 
可见,路由的结构是十分清晰的。
添加404页面
如果我们在浏览器中随意输入一个不存在的路径,比如http://localhost:4200/abc,会发现页面是空白的。这是因为我们没有处理404页面。我们可以在app-routing.module.ts中添加一个**路径,用来匹配所有路径,然后将其重定向到一个404组件。
创建404页面组件
| 1 | ng generate component not-found | 
修改404页面内容
打开not-found.component.html,输入以下内容。
| 1 | <p>404: Not Found!</p> | 
添加404路由
| 1 | // app-routing.module.ts | 
此时,如果在浏览器里面输入一个不存在的路径,比如http://localhost:4200/abc,则会显示404: Not Found!。
嵌套路由
如果我们想要在product组件中添加两个子路由,product-a和product-a,可以在app-routing.module.ts中添加如下代码。
新建两个组件
| 1 | ng generate component product-a | 
在product页面添加router-outlet
这个router-outlet用来显示ProductA和ProductB对应的的组件。
| 1 | <!-- product.component.html --> | 
添加子路由
| 1 | const routes: Routes = [ | 
此时,如果在浏览器中输入http://localhost:4200/product/a,会显示ProductA works!,输入http://localhost:4200/product/b,会显示ProductB works!。同时也会显示product works!,因为product.component.html中除了有<router-outlet></router-outlet>。还有<p>product works!</p>。
严格匹配
上面的例子中,product的path不能使用严格匹配选项pathMath: 'full',否则子路由无法匹配,会导致404。这是为什么呢?
因为匹配子路由的时候,必须先匹配父级路由,而由于父级路由使用了pathMatch: 'full',所以无法匹配,父级路由无法匹配导致子路由的匹配无法进行,所以导致了404!
如果你非要使用pathMatch: 'full',可以如下定义路由表。
| 1 | const routes: Routes = [ | 
这样就可以使用pathMatch: 'full'了。但是这么做也有一个弊端,就是a和b从逻辑上讲不再是product子路由了。而是平级路由,只不过路径中包含了字符串product而已。这会导致product页面的内容无法显示。通常父级路由都会显示一些公共信息,差异化的信息放到子路由中。如果你使用了pathMatch: 'full',则会导致父级页面内容无法显示。请酌情使用此方法。
注意,如果你使用了嵌套路由,或者lazy load, 请谨慎使用pathMatch: 'full'。详情请看这里:detail
路由拆分
如果product中的子路由越来越多,都放到一个文件中会显得很臃肿。这时候我们可以将子路由拆分到单独的文件中。将子路由定义到product组件中。
基于NgModule的路由拆分
进入product文件夹,执行以下命令。
| 1 | ng generate module product-routing --flat | 
--flat选项表示不创建文件夹。
在product-routing.module.ts中添加如下代码。
| 1 | // product-routing.module.ts | 
product.module.ts文件中添加如下代码,导入ProductRoutingModule。
| 1 | import {NgModule} from '@angular/core'; | 
修改app-routing.module.ts
注意,这里必须使用loadChildren, 否则子路由无法显示。
| 1 | // app-routing.module.ts | 
这样就可以将product中的子路由拆分到单独的文件中了。
基于Standalone组件的路由拆分
如果你的组件是standalone的,没有对应的module文件,那么可以使用如下方法拆分路由。注意loadChildren中加载的是路由文件,而不是module文件,也不是component文件。
| 1 | // app.routers.ts | 
在Product组件文件夹下新建路由文件product.routers.ts,添加如下代码。
| 1 | // product.routers.ts | 
注意这种方式下,点击product-detail链接时,会显示product-detail组件。product组件的内容不会显示。
如果要显示product组件的内容,需要将product-detail组件定义为product组件的子路由。如下所示。这种方式在导航时很方便,比如左侧是导航菜单,点击不同菜单显示不同的组件。对于整个页面来说,导航菜单一直显示,而右侧的内容会随着导航的变化而变化。
| 1 | // product.routers.ts | 
监听路由变化
如果我们想要在路由变化的时候做一些事情,比如打印路由信息,可以在app.component.ts中添加如下代码。此时会监听整个app所有路由的变化,如果只想监控某个组件的路由变化,那么可以在该组件中添监听代码。
| 1 | // app.component.ts | 
路由变化时会依次触发以下事件:
- NavigationStart
- RoutesRecognized
- GuardsCheckStart
- ChildActivationStart
- ActivationStart
- GuardsCheckEnd
- ResolveStart
- ResolveEnd
- ActivationEnd
- ChildActivationEnd
- NavigationEnd
我们一般在NavigationEnd事件中做一些事情。
| 1 | router.events.subscribe((event) => { | 
注意:
即使使用了skipLocationChange选项,也可以通过以上方法获取完整的路由信息,因为skipLocationChange只是不改变浏览器地址栏的URL,但是路由信息还是会变化的。
获取路由信息
如果想要获取路由对应的url,路由中的参数等信息,可以在组件中注入ActivatedRoute服务。
使用ActivatedRoute.snapshot获取路由信息
| 1 | // app.routers.ts | 
| 1 | <!-- product.component.html --> | 
| 1 | // product-detail.component.ts | 
使用ActivatedRoute.params订阅路由变化
| 1 | // product-detail.component.ts | 
snapshop和params的区别:
- snapshot只会获取一次路由信息,它是静态的,获取之后就不会再变化了。
- params会订阅路由变化,当路由变化时,会重新获取路由信息。
params和queryParams的区别:
- params: 获取路由中的参数,比如/product/1中的1。
- queryParams: 获取路由中的查询参数,比如/product?id=1&name=zdd中的id,name。
- matrixParameters: 获取路由中的矩阵参数,比如/product;id=1;name=zdd中的id,name。
queryParams和matrixParameters的区别
- queryParams: 使用?开头,使用&分隔每组参数,比如/product?id=1&name=zdd。
- matrixParameters: 使用;开头,使用;分隔每组参数,比如/product;id=1;name=zdd。
- 两者都以=分隔键值对。
路由守卫
待整理
路由传递数据
定义Router时发送数据
| 1 | // app.routers.ts | 
组件中接收数据
| 1 | // product.component.ts | 
navigation时发送数据
| 1 | this.router.navigate(['/product'], {state: {category: 'digital'}}); | 
组件中接收数据
After Angular 16, must call this on constructor or you will get null, see here for detail
| 1 | // product.component.ts | 
定义routerLink时发送数据
| 1 | <a [routerLink]="['/product']" [state]="{category: 'digital'}">Product</a> | 
组件中接收数据
| 1 | // product.component.ts | 
保持Fragment不丢失
Url中#后面紧跟的内容为Fragment,比如http://localhost:4200/book#b123中的b123就是Fragment。
首先在Component中定义Fragment变量
| 1 | export class AppComponent implements OnInit { | 
在routerLink中添加Fragment
| 1 | <a [routerLink]="['book', {}]" [fragment]="bookId">Book</a> | 
在navigate中添加Fragment
| 1 | this.router.navigate(['book'], {fragment: this.bookId}); | 
激活后路由为:http://localhost:4200/book#b123
保持Fragment不变
当我们从一个带Fragment的路由跳转到其他路由时(比如辅助路由),Fragment会丢失。如果我们想要保持Fragment不变,可以在navigate中添加preserveFragment选项。
| 1 | this.router.navigate([{outlets: {primary: 'book/book1/book2', detail: 'book-detail'}}, {preserveFragment: true}]); | 
此时路由变为:http://localhost:4200/book(detail:book-detail//list:book-list)#b123 - 注意Fragment在url末尾。
注意关闭辅助路由时,也要添加此选项,否则Fragment仍会丢失。
| 1 | this.router.navigate([{outlets: {list: null}}], {preserveFragment: true}); | 
此时路由变为:http://localhost:4200/book(detail:book-detail)#b123
| 1 | this.router.navigate([{outlets: {detail: null}}], {preserveFragment: true}); | 
此时路由变为:http://localhost:4200/book#b123
路由三要素
- 路由对应的路径及组件 - Routes配置文件
- 路由显示的位置 - Outlet
- 路由的触发方式- 点击触发,比如routerLink,button等。
- 编程触发,比如navigate,navigateByUrl等。
- 动态加载,比如动态创建组件并加载。
 
- 点击触发,比如
待商榷:
- 没有module时如何拆分router文件? - 可以直接使用路由module文件,不需要组件对应的module文件。目前standalone组件无法做到路由文件的拆分,必须有module支持。
https://www.angulararchitects.io/en/blog/routing-and-lazy-loading-with-standalone-components/