阅读(4015) (0)

Angular 教程:为英雄之旅添加路由支持-里程碑 1:起步

2022-07-04 13:44:14 更新

里程碑 1:起步

开始本应用的一个简版,它在两个空路由之间导航。


创建一个范例应用

  1. 创建一个新的 Angular 项目 angular-router-tour-of-heroes
  2. ng new angular-router-tour-of-heroes

    当系统提示 ​Would you like to add Angular routing?​ 时,选择 ​N​。

    当系统提示 ​Which stylesheet format would you like to use?​ 时,选择 ​CSS​。

    片刻之后,一个新项目 ​angular-router-tour-of-heroes​ 已准备就绪。

  3. 从你的终端,导航到 ​angular-router-tour-of-heroes​ 目录。
  4. 运行 ​ng serve​ 来验证新应用是否正常运行。
  5. ng serve
  6. 打开浏览器访问 ​http://localhost:4200​。
  7. 你会发现本应用正运行在浏览器中。

定义路由

路由器必须用“路由定义”的列表进行配置。

每个定义都被翻译成了一个Route对象。该对象有一个 ​path ​字段,表示该路由中的 URL 路径部分,和一个 ​component ​字段,表示与该路由相关联的组件。

当浏览器的 URL 变化时或在代码中告诉路由器导航到一个路径时,路由器就会翻出它用来保存这些路由定义的注册表。

第一个路由执行以下操作:

  • 当浏览器地址栏的 URL 变化时,如果它匹配上了路径部分 ​/crisis-center​,路由器就会激活一个 ​CrisisListComponent ​的实例,并显示它的视图。
  • 当应用程序请求导航到路径 ​/crisis-center​ 时,路由器激活一个 ​CrisisListComponent ​的实例,显示它的视图,并将该路径更新到浏览器地址栏和历史。

第一个配置定义了由两个路由构成的数组,它们用最短路径指向了 ​CrisisListComponent ​和 ​HeroListComponent​。

生成 ​CrisisList ​和 ​HeroList ​组件,以便路由器能够渲染它们。

ng generate component crisis-list
ng generate component hero-list

把每个组件的内容都替换成下列范例 HTML。

  • src/app/crisis-list/crisis-list.component.html
  • <h2>CRISIS CENTER</h2>
    <p>Get your crisis here</p>
  • src/app/hero-list/hero-list.component.html
  • <h2>HEROES</h2>
    <p>Get your heroes here</p>

注册 Router 和 Routes

为了使用 ​Router​,你必须注册来自 ​@angular/router​ 包中的 ​RouterModule​。定义一个路由数组 ​appRoutes​,并把它传给 ​RouterModule.forRoot()​ 方法。​RouterModule.forRoot()​ 方法会返回一个模块,其中包含配置好的 ​Router ​服务提供者,以及路由库所需的其它提供者。一旦启动了应用,​Router ​就会根据当前的浏览器 URL 进行首次导航。

注意:
RouterModule.forRoot()​ 方法是用于注册全应用级提供者的编码模式。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroListComponent } from './hero-list/hero-list.component';

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes', component: HeroListComponent },
];

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
  declarations: [
    AppComponent,
    HeroListComponent,
    CrisisListComponent,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
对于最小化的路由配置,把配置好的 ​RouterModule ​添加到 ​AppModule ​中就足够了。但是,随着应用的成长,你将需要将路由配置重构到单独的文件中,并创建路由模块,路由模块是一种特殊的、专做路由的服务模块。

把 ​RouterModule.forRoot()​ 注册到 ​AppModule ​的 ​imports ​数组中,能让该 ​Router ​服务在应用的任何地方都能使用。

添加路由出口

根组件 ​AppComponent ​是本应用的壳。它在顶部有一个标题、一个带两个链接的导航条,在底部有一个路由器出口,路由器会在它所指定的位置上渲染各个组件。


路由出口扮演一个占位符的角色,表示路由组件将会渲染到哪里。

该组件所对应的模板是这样的:

<h1>Angular Router</h1>
<nav>
  <a routerLink="/crisis-center" routerLinkActive="active" ariaCurrentWhenActive="page">Crisis Center</a>
  <a routerLink="/heroes" routerLinkActive="active" ariaCurrentWhenActive="page">Heroes</a>
</nav>
<router-outlet></router-outlet>

定义通配符路由

你以前在应用中创建过两个路由,一个是 ​/crisis-center​,另一个是 ​/heroes​。所有其它 URL 都会导致路由器抛出错误,并让应用崩溃。

可以添加一个通配符路由来拦截所有无效的 URL,并优雅的处理它们。 通配符路由的 ​path ​是两个星号(​**​),它会匹配任何 URL。 而当路由器匹配不上以前定义的那些路由时,它就会选择这个通配符路由。 通配符路由可以导航到自定义的“404 Not Found”组件,也可以重定向到一个现有路由。

路由器会使用先到先得的策略来选择路由。由于通配符路由是最不具体的那个,因此务必确保它是路由配置中的最后一个路由。

要测试本特性,请往 ​HeroListComponent ​的模板中添加一个带 ​RouterLink ​的按钮,并且把它的链接设置为一个不存在的路由 ​"/sidekicks"​。

<h2>HEROES</h2>
<p>Get your heroes here</p>

<button type="button" routerLink="/sidekicks">Go to sidekicks</button>

当用户点击该按钮时,应用就会失败,因为你尚未定义过 ​"/sidekicks"​ 路由。

不要添加 ​"/sidekicks"​ 路由,而是定义一个“通配符”路由,让它导航到 ​PageNotFoundComponent ​组件。

{ path: '**', component: PageNotFoundComponent }

创建 ​PageNotFoundComponent​,以便在用户访问无效网址时显示它。

ng generate component page-not-found
<h2>Page not found</h2>

现在,当用户访问 ​/sidekicks​ 或任何无效的 URL 时,浏览器就会显示“Page not found”。浏览器的地址栏仍指向无效的 URL。

设置跳转

应用启动时,浏览器地址栏中的初始 URL 默认是这样的:

localhost:4200

它不能匹配上任何硬编码进来的路由,于是就会走到通配符路由中去,并且显示 ​PageNotFoundComponent​。

这个应用需要一个有效的默认路由,在这里应该用英雄列表作为默认页。当用户点击"Heroes"链接或把 ​localhost:4200/heroes​ 粘贴到地址栏时,它应该导航到列表页。

添加一个 ​redirect ​路由,把最初的相对 URL(​''​)转换成所需的默认路径(​/heroes​)。

在通配符路由上方添加一个默认路由。在下方的代码片段中,它出现在通配符路由的紧上方,展示了这个里程碑的完整 ​appRoutes​。

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',        component: HeroListComponent },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

浏览器的地址栏会显示 ​.../heroes​,好像你直接在那里导航一样。

重定向路由需要一个 ​pathMatch ​属性,来告诉路由器如何用 URL 去匹配路由的路径。在本应用中,路由器应该只有在完整的 URL等于 ​''​ 时才选择 ​HeroListComponent ​组件,因此要把 ​pathMatch ​设置为 ​'full'​。

聚焦 PATHMATCH
从技术角度看,​pathMatch = 'full'​ 会导致 URL 中剩下的、未匹配的部分必须等于 ​''​。在这个例子中,跳转路由在一个顶层路由中,因此剩下的URL 和完整的URL 是一样的。
pathMatch ​的另一个可能的值是 ​'prefix'​,它会告诉路由器:当剩下的URL 以这个跳转路由中的 ​prefix ​值开头时,就会匹配上这个跳转路由。但这不适用于此示例应用,因为如果 ​pathMatch ​值是 ​'prefix'​,那么每个 URL 都会匹配 ​''​。
尝试把它设置为 ​'prefix'​,并点击 ​Go to sidekicks​ 按钮。这是因为它是一个无效 URL,本应显示“Page not found”页。但是,你仍然在“英雄列表”页中。在地址栏中输入一个无效的 URL,你又被路由到了 ​/heroes​。每一个 URL,无论有效与否,都会匹配上这个路由定义。
默认路由应该只有在整个 URL 等于 ​''​ 时才重定向到 ​HeroListComponent​,别忘了把重定向路由设置为 ​pathMatch = 'full'​。

里程碑 1 小结

当用户单击某个链接时,该示例应用可以在两个视图之间切换。

里程碑 1 涵盖了以下几点的做法:

  • 加载路由库
  • 往壳组件的模板中添加一个导航条,导航条中有一些 A 标签、​routerLink ​指令和 ​routerLinkActive ​指令
  • 往壳组件的模板中添加一个 ​router-outlet​ 指令,视图将会被显示在那里
  • 用 ​RouterModule.forRoot()​ 配置路由器模块
  • 设置路由器,使其合成 HTML5 模式的浏览器 URL
  • 使用通配符路由来处理无效路由
  • 当应用在空路径下启动时,导航到默认路由

这个初学者应用的结构是这样的:


下面是本里程碑中的文件列表。

  • app.component.html
  • <h1>Angular Router</h1>
    <nav>
      <a routerLink="/crisis-center" routerLinkActive="active" ariaCurrentWhenActive="page">Crisis Center</a>
      <a routerLink="/heroes" routerLinkActive="active" ariaCurrentWhenActive="page">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  • app.module.ts
  • import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    import { RouterModule, Routes } from '@angular/router';
    
    import { AppComponent } from './app.component';
    import { CrisisListComponent } from './crisis-list/crisis-list.component';
    import { HeroListComponent } from './hero-list/hero-list.component';
    import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
    
    const appRoutes: Routes = [
      { path: 'crisis-center', component: CrisisListComponent },
      { path: 'heroes', component: HeroListComponent },
    
      { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
      { path: '**', component: PageNotFoundComponent }
    ];
    
    @NgModule({
      imports: [
        BrowserModule,
        FormsModule,
        RouterModule.forRoot(
          appRoutes,
          { enableTracing: true } // <-- debugging purposes only
        )
      ],
      declarations: [
        AppComponent,
        HeroListComponent,
        CrisisListComponent,
        PageNotFoundComponent
      ],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }
  • hero-list/hero-list.component.html
  • <h2>HEROES</h2>
    <p>Get your heroes here</p>
    
    <button type="button" routerLink="/sidekicks">Go to sidekicks</button>
  • crisis-list/crisis-list.component.html
  • <h2>CRISIS CENTER</h2>
    <p>Get your crisis here</p>
  • page-not-found/page-not-found.component.html
  • <h2>Page not found</h2>
  • index.html
  • <html lang="en">
      <head>
        <!-- Set the base href -->
        <base href="/">
        <title>Angular Router</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
      </head>
    
      <body>
        <app-root></app-root>
      </body>
    
    </html>