Angular8 路由和导航
导航是 Web 应用程序中的重要方面之一。即使单页应用程序 (SPA) 没有多页概念,它确实从一个视图(费用清单)移动到另一个视图(费用明细)。提供清晰易懂的导航元素决定了应用程序的成功。
Angular 提供了广泛的导航功能集,以适应简单场景到复杂场景。定义导航元素和相应视图的过程称为
路由。 Angular 提供了一个单独的模块
RouterModule 来在 Angular 应用程序中设置导航。让我们在本章中学习如何在 Angular 应用程序中进行路由。
配置路由
Angular CLI 为在应用程序创建过程和应用程序工作期间设置路由提供完整支持。让我们使用以下命令创建一个启用路由器的新应用程序-
ng new routing-app--routing
Angular CLI 生成一个新模块 AppRoutingModuele 用于路由目的。生成的代码如下-
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
这里,
从@angular/router 包导入 RouterModule 和路由。
RouterMoudle 提供在应用程序中配置和执行路由的功能。
Routes 是用于设置导航规则的类型。
Routes 是用于配置应用程序实际导航规则的局部变量(属于 Routes 类型)。
RouterMoudle.forRoot() 方法将设置路由变量中配置的导航规则。
Angular CLI 将生成的 AppRoutingModule 包含在 AppComponent 中,如下所述-
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
这里,
AppComponent 使用导入元数据导入
AppRoutingModule 模块。
Angular CLI 也提供了在现有应用程序中设置路由的选项。在现有应用程序中包含路由的一般命令如下-
ng generate module my-module--routing
这将生成启用路由功能的新模块。要在现有模块 (AppModule) 中启用路由功能,我们需要包含如下指定的额外选项-
ng generate module app-routing--module app--flat
这里,
–module app 在 AppModule 模块中配置新创建的路由模块,
AppRoutingModule。
让我们在
ExpenseManager 应用程序中配置路由模块。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
使用以下命令生成路由模块-
ng generate module app-routing--module app--flat
输出
输出如下-
CREATE src/app/app-routing.module.ts (196 bytes)
UPDATE src/app/app.module.ts (785 bytes)
这里,
CLI 生成
AppRoutingModule,然后在
AppModule
中进行配置
创建路由
创建路线简单易行。创建路线的基本信息如下-
要调用的目标组件。
访问目标组件的路径。
创建简单路由的代码如下-
const routes: Routes = [
{ path: 'about', component: AboutComponent },
];
这里,
Routes 是 AppRoutingModule 中的变量。
about 是路径,AboutComponent 是目标/目标组件。当用户请求 http://localhost:4200/about url 时,路径与 about 规则匹配,然后 AboutComponent 将被调用。
访问路线
让我们学习如何在应用程序中使用配置的路由。
访问路线是一个两步过程。
在根组件模板中包含
router-outlet 标签。
<router-outlet></router-outlet>
在所需位置使用
routerLink 和
routerLinkActive 属性。
<a routerLink="/about" routerLinkActive="active">First Component</a>
这里,
routerLink 设置要使用路径调用的路由。
routerLinkActive 设置路由激活时使用的 CSS 类。
有时,我们需要访问组件内部的路由而不是模板。然后,我们需要按照以下步骤操作-
在相应的组件中注入
Router和
ActivatedRoute的实例。
import { Router, ActivatedRoute } from '@angular/router';
constructor(private router: Router, private route: ActivatedRoute)
这里,
Router 提供了执行路由操作的功能。
Route 指的是当前的激活路由。
使用路由器的导航功能。
this.router.navigate(['about']);
这里,
navigate 函数需要一个包含必要路径信息的数组。
使用相对路径
路由路径类似于网页网址,也支持相对路径。要从另一个组件访问
AboutComponent,例如
HomePageComponent,简单地使用 .. 表示法,如 web url 或文件夹路径。
<a routerLink="../about">Relative Route to about component</a>
访问组件中的相对路径-
import { NavigationExtras } from '@angular/router';
this.router.navigate(['about'], { relativeTo: this.route });
这里,
relativeTo 在
NavigationExtras 类下可用。
路由排序
路由排序 在路由配置中非常重要。如果多次配置相同的路径,则将调用第一个匹配的路径。如果第一场比赛由于某种原因失败,那么第二场比赛将被调用。
重定向路由
Angular 路由允许将路径重定向到另一个路径。
redirectTo 是设置重定向路径的选项。示例路线如下-
const routes: Routes = [
{ path: '', redirectTo: '/about' },
];
这里,
redirectTo 如果实际路径匹配空字符串,则设置为重定向路径。
通配符路线
通配符路由将匹配任何路径。它是使用 ** 创建的,将用于处理应用程序中不存在的路径。将通配符路由放在配置的末尾,使其在其他路径不匹配时调用。
示例代码如下-
const routes: Routes = [
{ path: 'about', component: AboutComponent },
{ path: '', redirectTo: '/about', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
这里,
如果调用了一个不存在的页面,那么前两条路由就会失败。但是,最终的通配符路由将会成功并且
PageNotFoundComponent 被调用。
访问路由参数
在 Angular 中,我们可以使用参数在路径中附加额外信息。可以使用 paramMap 接口在组件中访问该参数。在路由中创建新参数的语法如下-
const routes: Routes = [
{ path: 'about', component: AboutComponent },
{ path: 'item/:id', component: ItemComponent },
{ path: '', redirectTo: '/about', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
在这里,我们在路径中附加了
id。可以使用两种技术在
ItemComponent 中访问
id。
使用 Observable。
使用快照(不可观察选项)。
使用 Observable
Angular 提供了一个特殊的接口,paramMap 来访问路径的参数。 parmaMap 有以下方法-
has(name)-如果指定的名称在路径(参数列表)中可用,则返回 true。
get(name)-返回路径中指定名称的值(参数列表)。
getAll(name)-返回路径中指定名称的多个值。当多个值可用时,get() 方法仅返回第一个值。
keys-返回路径中可用的所有参数。
使用
paramMap访问参数的步骤如下-
导入 @angular/router 包中的 paramMap。
在 ngOnInit() 中使用 paramMap 访问参数并将其设置为局部变量。
ngOnInit() {
this.route.paramMap.subscribe(params => {
this.id = params.get('id);
});
}
我们可以使用
pipe方法直接在rest服务中使用它。
this.item$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = Number(params.get('id'));
return this.service.getItem(this.selectedId);
})
);
使用快照
snapshot 与
Observable 类似,不同之处在于它不支持 observable 并立即获取参数值。
let id = this.route.snapshot.paramMap.get('id');
嵌套路由
一般来说,
router-outlet 会被放置在应用程序的根组件
(AppComponent) 中。但是,路由器插座可用于任何组件。当路由器出口用于除根组件之外的组件中时,特定组件的路由必须配置为父组件的子组件。这称为
嵌套路由。
让我们考虑一个组件,比如
ItemComponent 配置了
router-outlet 并且有两个
routerLink 如下所示-
<h2>Item Component</h2>
<nav>
<ul>
<li><a routerLink="view">View</a></li>
<li><a routerLink="edit">Edit</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
ItemComponent 的路由必须配置为
嵌套路由,如下所示-
const routes: Routes = [
{
path: 'item',
component: ItemComponent,
children: [
{
path: 'view',
component: ItemViewComponent
},
{
path: 'edit',
component: ItemEditComponent
}
]
}]
工作示例
让我们将本章中学到的路由概念应用到我们的
ExpenseManager 应用程序中。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
使用下面的命令生成路由模块,如果之前没有完成。
ng generate module app-routing--module app--flat
输出
输出如下-
CREATE src/app/app-routing.module.ts (196 bytes) UPDATE src/app/app.module.ts (785 bytes)
这里,
CLI 生成
AppRoutingModule,然后在
AppModule 中进行配置。
更新
AppRoutingModule (src/app/app.module.ts),如下所述-
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { ExpenseEntryComponent } from './expense-entry/expense-entry.component';
import { ExpenseEntryListComponent } from './expense-entry-list/expense-entry-list.component';
const routes: Routes = [
{ path: 'expenses', component: ExpenseEntryListComponent },
{ path: 'expenses/detail/:id', component: ExpenseEntryComponent },
{ path: '', redirectTo: 'expenses', pathMatch: 'full' }];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule] })
export class AppRoutingModule { }
在这里,我们为我们的费用列表和费用详细信息组件添加了路由。
更新
AppComponent 模板
(src/app/app.component.html) 以包含
router-outlet 和
routerLink。
<!--Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="#">{{ title }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home
<span class="sr-only" routerLink="/">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/expenses">Report</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Add Expense</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
</ul>
</div>
</div>
</nav>
<router-outlet></router-outlet>
打开
ExpenseEntryListComponent 模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并包括每个费用条目的查看选项。
<table class="table table-striped">
<thead>
<tr>
<th>Item</th>
<th>Amount</th>
<th>Category</th>
<th>Location</th>
<th>Spent On</th>
<th>View</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let entry of expenseEntries">
<th scope="row">{{ entry.item }}</th>
<th>{{ entry.amount }}</th>
<td>{{ entry.category }}</td>
<td>{{ entry.location }}</td>
<td>{{ entry.spendOn | date: 'medium' }}</td>
<td><a routerLink="../expenses/detail/{{ entry.id }}">View</a></td>
</tr>
</tbody>
</table>
在这里,我们更新了费用清单并添加了一个新列来显示查看选项。
打开
ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.ts) 并添加功能以获取当前选定的费用条目。它可以通过首先获得通过
paramMap 获取 id,然后使用
ExpenseEntryService 中的
getExpenseEntry() 方法。
this.expenseEntry$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = Number(params.get('id'));
return
this.restService.getExpenseEntry(this.selectedId); }));
this.expenseEntry$.subscribe( (data) => this.expenseEntry = data );
更新 ExpenseEntryComponent 并添加进入费用列表的选项。
goToList() {
this.router.navigate(['/expenses']);
}
ExpenseEntryComponent 的完整代码如下-
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { ExpenseEntryService } from '../expense-entry.service';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Component({
selector: 'app-expense-entry',
templateUrl: './expense-entry.component.html',
styleUrls: ['./expense-entry.component.css']
})
export class ExpenseEntryComponent implements OnInit {
title: string;
expenseEntry$ : Observable<ExpenseEntry>;
expenseEntry: ExpenseEntry = {} as ExpenseEntry;
selectedId: number;
constructor(private restService : ExpenseEntryService, private router : Router, private route :
ActivatedRoute ) { }
ngOnInit() {
this.title = "Expense Entry";
this.expenseEntry$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = Number(params.get('id'));
return
this.restService.getExpenseEntry(this.selectedId); }));
this.expenseEntry$.subscribe( (data) => this.expenseEntry = data );
}
goToList() {
this.router.navigate(['/expenses']);
}
}
打开
ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.html) 模板并添加一个新按钮以导航回费用列表页面。
<div class="col-sm" style="text-align: right;">
<button type="button" class="btn btn-primary" (click)="goToList()">Go to List</button>
<button type="button" class="btn btn-primary">Edit</button>
</div>
这里,我们在
Edit 按钮之前添加了
Go to List 按钮。
使用以下命令运行应用程序-
应用程序的最终输出如下-
单击第一个条目的查看选项将导航到详细信息页面并显示所选费用条目,如下所示-