Angular 8 - 路由和导航

导航是网络应用程序的重要方面之一。 尽管单页面应用程序 (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()方法将设置在routes变量中配置的导航规则。

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>

在所需位置使用routerLinkrouterLinkActive属性。

<a routerLink="/about" routerLinkActive="active">First Component</a>

这里,

  • routerLink使用路径设置要调用的路由。

  • routerLinkActive 设置激活路由时要使用的 CSS 类。

有时,我们需要访问组件内部的路由而不是模板。 然后,我们需要执行以下步骤 −

在相应的组件中注入RouterActivatedRoute的实例。

import { Router, ActivatedRoute } from '@angular/router'; 
constructor(private router: Router, private route: ActivatedRoute)

这里,

  • Router提供了进行路由操作的功能。

  • route指当前活动路由

使用路由器的导航功能。

this.router.navigate(['about']);

这里,

navigate 函数需要一个包含必要路径信息的数组。

使用相对路径

路由路径与网页URL类似,也支持相对路径。 要从另一个组件(例如 HomePageComponent)访问 AboutComponent,只需在 Web url 或文件夹路径中使用 .. 表示法即可。

<a routerLink="../about">Relative Route to about component</a>

访问组件中的相对路径 −

import { NavigationExtras } from '@angular/router'; 
this.router.navigate(['about'], { relativeTo: this.route });

这里,

relativeToNavigationExtras 类下可用。

路由排序

路由排序在路由配置中非常重要。 如果多次配置相同的路径,则将调用第一个匹配的路径。 如果第一场比赛由于某种原因失败,那么第二场比赛将被调用。

重定向路由

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(可观察)。
  • 使用 snapshot(快照、不可观察选项)。

使用 Observable

Angular 提供了一个特殊的接口 paramMap 来访问路径的参数。 parmaMap 有以下方法 −

  • has(name) − 如果指定名称在路径(参数列表)中可用,则返回 true。

  • get(name) − 返回路径(参数列表)中指定名称的值。

  • getAll(name) − 返回路径中指定名称的多个值。 当有多个值可用时,get() 方法仅返回第一个值。

  • keys − 返回路径中所有可用的参数。

使用paramMap访问参数的步骤如下 −

  • 导入paramMap,可在@angular/router包中使用。

  • ngOnInit()中使用paramMap来访问参数并将其设置为局部变量。

ngOnInit() {
    this.route.paramMap.subscribe(params => {
        this.id = params.get('id);
    });
}

我们可以使用 pipe 方法直接在其余服务中使用它。

this.item$ = this.route.paramMap.pipe(
    switchMap(params => {
      this.selectedId = Number(params.get('id'));
      return this.service.getItem(this.selectedId);
    })
);

使用snapshot(快照)

snapshotObservable 类似,但它不支持 observable 并立即获取参数值。

let id = this.route.snapshot.paramMap.get('id');

嵌套路由

一般来说,router-outlet会被放置在应用程序的根组件(AppComponent)中。 但是,router-outlet 可以在任何组件中使用。 当在根组件以外的组件中使用 router-outlet 时,必须将特定组件的路由配置为父组件的子组件。 这称为嵌套路由

让我们考虑一个组件,假设 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-outletrouterLink

<!-- 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 );

Update ExpenseEntryComponent and add option to go to expense list.

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按钮。

使用以下命令运行应用程序 −

ng serve

应用程序的最终输出如下 −

嵌套路由

点击第一个条目的查看选项将导航到详细信息页面并显示所选费用条目,如下所示 −

嵌套路由