0%

angular-auxiliary-secondary-router

Angular对路由的支持非常强大,可以实现多种路由模式,本文主要介绍辅助路由。

辅助路由

辅助路由(auxiliary route)是一种特殊的路由,它的主要应用场景是为当前页面添加弹窗,主路由和辅助路由对应的组件同时显示,当关闭辅助路由组件(弹窗)时,主路由仍然保持显示。

实现步骤

创建Project

首先参考这篇创建一个Angular项目。注意从Angular 17开始,默认就使用Standalone Component来创建组件了,不在使用NgModule了。

添加路由配置

打开app.routes.ts文件,添加路由配置:

1
2
3
4
export const routes: Routes = [
{path: 'book', component: BookComponent},
{path: 'book-detail', outlet: 'detail', component: BookDetailComponent},
];

添加router-outlet

app.component.html文件中添加router-outlet:从Angular17开始,可以使用自关闭组件了,也就是说<router-outlet></router-outlet>可以简化为<router-outlet />,注意第二个outlet添加了name属性,用来给outlet命名。

1
2
<router-outlet />
<router-outlet name="detail"/>

添加book组件

1
ng generate component book

book组件的模板文件中添加一个按钮,点击按钮时显示book-detail组件:

打开book.component.html,添加如下代码:

1
2
<p>book works!</p>
<button (click)="openBookDetail()">Book Detail</button>

打开book.component.ts,添加按钮点击事件处理函数,这里调用navigate方法来实现路由跳转。注意该方法的第一个参数是数组,如果要跳转到命名的outlet, 则格式为:

1
[{outlets: {outlet-name: 'path-name'}}]

以下代码中: detail为命名的outletbook-detailpath-name

1
2
3
4
5
6
7
8
export class BookComponent {
constructor(private router: Router) {
}

openBookDetail() {
this.router.navigate([{outlets: {detail: 'book-detail'}}]);
}
}

添加book-detail组件

1
ng generate component book-detail

打开book-detail.component.html,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
<div class="book-detail">
<h1 class="book-detail-title">Book Detail</h1>
<div class="book-detail-body">
<p>Book name: XXX</p>
<p>Author: XXX</p>
<p>Price: XXX</p>
<p>Category: XXX</p>
<p>Published date: XXX</p>
<p>Quantity: XXX</p>
</div>
<button class="close-button" (click)="closeBookDetail()">X</button>
</div>

打开book-detail.component.ts,添加按钮点击事件处理函数:这里将path设置为null,表示关闭对应的组件。

1
2
3
4
5
6
7
8
export class BookDetailComponent {
constructor(private router: Router) {
}

closeBookDetail() {
this.router.navigate([{outlets: {detail: null}}]);
}
}

运行项目

1
ng serve

打开浏览器,访问http://localhost:4200/book

alt text

点击Book Detail按钮,弹出book-detail组件,此时路由变华为https://localhost:4200/book(detail:book-detail)

  • detail: 表示辅助路由的名称,定义在outlet属性中。
  • book-detail: 表示辅助路由的路径。定义在router文件中。

alt text

点击弹窗上的关闭按钮,关闭book-detail组件,路由恢复为http://localhost:4200/book

同时显示多个辅助路由。

添加一个book-list组件,点击Book List按钮时显示book-list弹窗。

1
ng generate component book-list

打开book-list.component.html文件,添加如下代码:

1
2
3
4
5
6
7
8
9
10
<div class="book-list">
<h1 class="book-list-title">Book List</h1>
<div class="book-list-body">
<p>Book 1</p>
<p>Book 2</p>
<p>Book 3</p>
<p>Book 4</p>
</div>
<button class="close-button" (click)="closeBookList()">X</button>
</div>

打开book-list.component.ts文件,添加如下代码:

1
2
3
4
5
6
7
8
export class BookListComponent {
constructor(private router: Router) {
}

closeBookList() {
this.router.navigate([{outlets: {list: null}}]);
}
}

打开book-list.component.css 添加样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.book-list {
position: absolute;
top: 30%;
left: 30%;
transform: translate(-30%, -30%);
z-index: 9;
display: flex;
flex-direction: column;
align-items: center;

width: 400px;
height: 400px;

background-color: white;
border-radius: 10px;
border: 1px solid gray;
box-shadow: 0 0 20px rgba(0,0,0,0.6);
}

.close-button {
position: absolute;
top: 16px;
right: 16px;
width: 40px;
}

打开book.component.html文件,添加Book List按钮:

1
2
3
<p>book works!</p>
<button (click)="openBookList()">Book List</button>
<button (click)="openBookDetail()">Book Detail</button>

打开book.component.ts文件,添加按钮点击事件处理函数:

1
2
3
openBookList() {
this.router.navigate([{outlets: {list: 'book-list'}}]);
}

app.routes.ts文件中添加路由配置:

1
2
3
4
5
export const routes: Routes = [
{path: 'book', component: BookComponent},
{path: 'book-list', outlet: 'list', component: BookListComponent},
{path: 'book-detail', outlet: 'detail', component: BookDetailComponent},
];

打开app.component.html文件,添加Book List对应的outlet

1
2
3
<router-outlet />
<router-outlet name="detail"/>
<router-outlet name="list" />

在次运行项目,依次点击Book ListBook Detail按钮,可以同时显示两个弹窗。观察此时路由的变化,注意有多个辅助路由时,按照路由outlet名字的字符串顺序显示,因为detail排在list前面,所以先显示detail,再显示list。无论先点击哪个按钮,路由顺序皆如此。

alt text

http://localhost:4200/book(detail:book-detail//list:book-list)

  • detail: 表示辅助路由的名称,定义在outlet属性中。
  • book-detail: 表示辅助路由的路径。定义在router文件中。
  • list: 表示辅助路由的名称,定义在outlet属性中。
  • book-list: 表示辅助路由的路径。定义在router文件中。
  • //: 用来分隔多个辅助路由。

不更改URL显示辅助路由

默认情况下,点击按钮后,路由会发生变化,URL会显示辅助路由的路径。如果不想更改URL,可以使用skipLocationChange选项。
book.component.ts文件中,添加按钮点击事件处理函数:

1
2
3
4
5
6
openBookList() {
this.router.navigate([{outlets: {list: 'book-list'}}], {skipLocationChange: true});
}
openBookDetail() {
this.router.navigate([{outlets: {detail: 'book-detail'}}], {skipLocationChange: true});
}

注意,如果有多个辅助路由,也要在关闭按钮点击事件处理函数中添加skipLocationChange选项,否则关闭一个弹窗时,另一个弹窗的URL会显示在地址栏中。

1
2
3
4
5
6
closeBookList() {
this.router.navigate([{outlets: {list: null}}], {skipLocationChange: true});
}
closeBookDetail() {
this.router.navigate([{outlets: {detail: null}}], {skipLocationChange: true});
}

再次点击Book ListBook Detail按钮,可以看到URL没有发生变化。依次关闭两个弹窗,URL仍然保持不变。

alt text

使用routerLink显示辅助路由

上面的例子中,我们通过点击按钮,并且在按钮事件处理函数中调用navigate方法来显示辅助路由。也可以使用routerLink来显示辅助路由。
app.component.html文件中,使用routerLink显示辅助路由:

1
2
<a [routerLink]="[{outlets: {list: 'book-list'}}]">Book List</a>
<a [routerLink]="[{outlets: {detail: 'book-detail'}}]">Book Detail</a>

主路由和辅助路由各自独立

前面提起过,主路由和辅助路由是平级关系,二者可自由变化,互补影响,比如我们可以在book组件下添加一个子路由book1,然后在book1下再添加子路由book2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app.routers.ts
export const routes: Routes = [
{
path: 'book',
component: BookComponent,
children: [
{
path: 'book1',
component: Book1Component,
children: [
{
path: 'book2',
component: Book2Component,
}
]
},
]
},
];
1
2
3
<!-- book.component.html -->
<p>book works!</p>
<router-outlet />
1
2
3
<!-- book1.component.html -->
<p>book1 works!</p>
<router-outlet />
1
2
<!-- book2.component.html -->
<p>book2 works!</p>

此时点击book-detail按钮,观察路由变化,辅助路由自动append到主路由后面,无论主路由的层级有多深。

1
http://localhost:4200/book/book1/book2(detail:book-detail)

同时显示主路由和辅助路由

主路由的outlet nameprimary,我们只需在routerLink或者navigate函数中指定primary即可。

通过routerLink属性触发(浏览器url:http://localhost:4200/book(detail:book-detail))

1
<a [routerLink]="[{outlets: {primary: 'book', detail: 'book-detail'}}]">Book and detail</a>

通过router.navigate方法触发(浏览器url:http://localhost:4200/book(detail:book-detail))

1
this.router.navigate([{outlets: {primary: 'book', detail: 'book-detail'}}]);

如果主路由对应多级path,直接指定即可,如下:(浏览器url:http://localhost:4200/book/book1/book2(detail:book-detail))

1
this.router.navigate([{outlets: {primary: 'book/book1/book2', detail: 'book-detail'}}]);

一次触发多个辅助路由

上面的例子中我们是依次点击按钮来显示辅助路由的,Angular也支持一次触发多个辅助路由,

可以在routerLink中同时定义多个辅助路由,在app.component.html文件中,添加如下代码,当我们点击Book List and Details按钮时,将同时显示book-listbook-detail组件。Url也将变为http://localhost:4200/book(detail:book-detail//list:book-list)

1
2
<!-- app.component.html -->
<a [routerLink]="[{outlets: {list: 'book-list', detail: 'book-detail'}}]">Book List and Details</a>

使用navigate方法

1
2
3
openBookListAndDetail() {
this.router.navigate([{outlets: {list: 'book-list', detail: 'book-detail'}}]);
}

总结

  • 辅助路由的格式:(outletname: path),比如(list:book-list), list对应outlet name, book-list对应path
  • 主路由和辅助路由是平级关系,他们的outlet要定义在一个文件中。比如上面例子中book和book-list,book-detail三者都是平级关系,所以他们的outlet都定义在app.component.html文件中。
  • outlet属性中name用来表示辅助路由的名称,不能包含-,不能是字符串primary,否则无法显示。
  • html文件中如果使用了routerLink,那么同时也要定义outlet,否则无法显示。