Route 子路由

2020-07-07 16:30 更新

本節(jié)將向你展示如何在應(yīng)用中添加子路由并使用相對(duì)路由。

要為應(yīng)用當(dāng)前的危機(jī)中心添加更多特性,請(qǐng)執(zhí)行類似于 heroes 特性的步驟:

  • 在 src/app 目錄下創(chuàng)建一個(gè) crisis-center 子目錄。

  • 把 app/heroes 中的文件和目錄復(fù)制到新的 crisis-center 文件夾中。

  • 在這些新建的文件中,把每個(gè) "hero" 都改成 "crisis",每個(gè) "heroes" 都改成 "crises"。

  • 把這些 NgModule 文件改名為 crisis-center.module.ts 和 crisis-center-routing.module.ts。

使用 mockcrises 來(lái)代替 mockheroes

Path:"src/app/crisis-center/mock-crises.ts" 。

import { Crisis } from './crisis';


export const CRISES: Crisis[] = [
  { id: 1, name: 'Dragon Burning Cities' },
  { id: 2, name: 'Sky Rains Great White Sharks' },
  { id: 3, name: 'Giant Asteroid Heading For Earth' },
  { id: 4, name: 'Procrastinators Meeting Delayed Again' },
]

最終的危機(jī)中心可以作為引入子路由這個(gè)新概念的基礎(chǔ)。 你可以把英雄管理保持在當(dāng)前狀態(tài),以便和危機(jī)中心進(jìn)行對(duì)比。

遵循關(guān)注點(diǎn)分離原則, 對(duì)危機(jī)中心的修改不會(huì)影響 AppModule 或其它特性模塊中的組件。

帶有子路由的危機(jī)中心

如何組織危機(jī)中心,來(lái)滿足 Angular 應(yīng)用所推薦的模式:

  • 把每個(gè)特性放在自己的目錄中。

  • 每個(gè)特性都有自己的 Angular 特性模塊。

  • 每個(gè)特性區(qū)都有自己的根組件。

  • 每個(gè)特性區(qū)的根組件中都有自己的路由出口及其子路由。

  • 特性區(qū)的路由很少(或完全不)與其它特性區(qū)的路由交叉。

如果你還有更多特性區(qū),它們的組件樹(shù)是這樣的:

子路由組件

crisis-center 目錄下生成一個(gè) CrisisCenter 組件:

ng generate component crisis-center/crisis-center

使用如下代碼更新組件模板:

Path:"src/app/crisis-center/crisis-center/crisis-center.component.html" 。

<h2>CRISIS CENTER</h2>
<router-outlet></router-outlet>

CrisisCenterComponentAppComponent 有下列共同點(diǎn):

它是危機(jī)中心特性區(qū)的根,正如 AppComponent 是整個(gè)應(yīng)用的根。

它是危機(jī)管理特性區(qū)的殼,正如 AppComponent 是管理高層工作流的殼。

就像大多數(shù)的殼一樣,CrisisCenterComponent 類是最小化的,因?yàn)樗鼪](méi)有業(yè)務(wù)邏輯,它的模板中沒(méi)有鏈接,只有一個(gè)標(biāo)題和用于放置危機(jī)中心的子組件的 <router-outlet>。

子路由配置

crisis-center 目錄下生成一個(gè) CrisisCenterHome 組件,作為 "危機(jī)中心" 特性的宿主頁(yè)面。

ng generate component crisis-center/crisis-center-home

用一條歡迎信息修改 Crisis Center 中的模板。

Path:"src/app/crisis-center/crisis-center-home/crisis-center-home.component.html" 。

<p>Welcome to the Crisis Center</p>

把 "heroes-routing.module.ts" 文件復(fù)制過(guò)來(lái),改名為 "crisis-center-routing.module.ts",并修改它。 這次你要把子路由定義在父路由 crisis-center 中。

Path:"src/app/crisis-center/crisis-center-routing.module.ts (Routes)" 。

const crisisCenterRoutes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisCenterComponent,
    children: [
      {
        path: '',
        component: CrisisListComponent,
        children: [
          {
            path: ':id',
            component: CrisisDetailComponent
          },
          {
            path: '',
            component: CrisisCenterHomeComponent
          }
        ]
      }
    ]
  }
];

注意,父路由 crisis-center 有一個(gè) children 屬性,它有一個(gè)包含 CrisisListComponent 的路由。 CrisisListModule 路由還有一個(gè)帶兩個(gè)路由的 children 數(shù)組。

這兩個(gè)路由分別導(dǎo)航到了危機(jī)中心的兩個(gè)子組件:CrisisCenterHomeComponentCrisisDetailComponent

對(duì)這些子路由的處理中有一些重要的差異。

路由器會(huì)把這些路由對(duì)應(yīng)的組件放在 CrisisCenterComponentRouterOutlet 中,而不是 AppComponent 殼組件中的。

CrisisListComponent 包含危機(jī)列表和一個(gè) RouterOutlet,用以顯示 Crisis Center HomeCrisis Detail 這兩個(gè)路由組件。

Crisis Detail 路由是 Crisis List 的子路由。由于路由器默認(rèn)會(huì)復(fù)用組件,因此當(dāng)你選擇了另一個(gè)危機(jī)時(shí),CrisisDetailComponent 會(huì)被復(fù)用。 作為對(duì)比,回頭看看 Hero Detail 路由,每當(dāng)你從列表中選擇了不同的英雄時(shí),都會(huì)重新創(chuàng)建該組件。

在頂層,以 / 開(kāi)頭的路徑指向的總是應(yīng)用的根。 但這里是子路由。 它們是在父路由路徑的基礎(chǔ)上做出的擴(kuò)展。 在路由樹(shù)中每深入一步,你就會(huì)在該路由的路徑上添加一個(gè)斜線 /(除非該路由的路徑是空的)。

如果把該邏輯應(yīng)用到危機(jī)中心中的導(dǎo)航,那么父路徑就是 "/crisis-center"。

要導(dǎo)航到 CrisisCenterHomeComponent,完整的 URL 是 /crisis-center (/crisis-center + '' + '')。

要導(dǎo)航到 CrisisDetailComponent 以展示 id=2 的危機(jī),完整的 URL 是 /crisis-center/2 (/crisis-center + '' + '/2')。

本例子中包含站點(diǎn)部分的絕對(duì) URL,就是:

localhost:4200/crisis-center/2

這里是完整的 "crisis-center.routing.ts" 及其導(dǎo)入語(yǔ)句。

Path:"src/app/crisis-center/crisis-center-routing.module.ts (excerpt)" 。

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';


import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent }       from './crisis-list/crisis-list.component';
import { CrisisCenterComponent }     from './crisis-center/crisis-center.component';
import { CrisisDetailComponent }     from './crisis-detail/crisis-detail.component';


const crisisCenterRoutes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisCenterComponent,
    children: [
      {
        path: '',
        component: CrisisListComponent,
        children: [
          {
            path: ':id',
            component: CrisisDetailComponent
          },
          {
            path: '',
            component: CrisisCenterHomeComponent
          }
        ]
      }
    ]
  }
];


@NgModule({
  imports: [
    RouterModule.forChild(crisisCenterRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class CrisisCenterRoutingModule { }

把危機(jī)中心模塊導(dǎo)入到 AppModule 的路由中

就像 HeroesModule 模塊中一樣,你必須把 CrisisCenterModule 添加到 AppModuleimports 數(shù)組中,就在 AppRoutingModule 前面:

  1. Path:"src/app/crisis-center/crisis-center.module.ts" 。

    import { NgModule }       from '@angular/core';
    import { FormsModule }    from '@angular/forms';
    import { CommonModule }   from '@angular/common';


    import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
    import { CrisisListComponent }       from './crisis-list/crisis-list.component';
    import { CrisisCenterComponent }     from './crisis-center/crisis-center.component';
    import { CrisisDetailComponent }     from './crisis-detail/crisis-detail.component';


    import { CrisisCenterRoutingModule } from './crisis-center-routing.module';


    @NgModule({
      imports: [
        CommonModule,
        FormsModule,
        CrisisCenterRoutingModule
      ],
      declarations: [
        CrisisCenterComponent,
        CrisisListComponent,
        CrisisCenterHomeComponent,
        CrisisDetailComponent
      ]
    })
    export class CrisisCenterModule {}

  1. Path:"src/app/app.module.ts (import CrisisCenterModule)" 。

    import { NgModule }       from '@angular/core';
    import { CommonModule }   from '@angular/common';
    import { FormsModule }    from '@angular/forms';


    import { AppComponent }            from './app.component';
    import { PageNotFoundComponent }   from './page-not-found/page-not-found.component';
    import { ComposeMessageComponent } from './compose-message/compose-message.component';


    import { AppRoutingModule }        from './app-routing.module';
    import { HeroesModule }            from './heroes/heroes.module';
    import { CrisisCenterModule }      from './crisis-center/crisis-center.module';


    @NgModule({
      imports: [
        CommonModule,
        FormsModule,
        HeroesModule,
        CrisisCenterModule,
        AppRoutingModule
      ],
      declarations: [
        AppComponent,
        PageNotFoundComponent
      ],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }

從 "app.routing.ts" 中移除危機(jī)中心的初始路由。 因?yàn)楝F(xiàn)在是 HeroesModuleCrisisCenter 模塊提供了這些特性路由。

"app-routing.module.ts" 文件中只有應(yīng)用的頂層路由,比如默認(rèn)路由和通配符路由。

Path:"src/app/app-routing.module.ts (v3)" 。

import { NgModule }                from '@angular/core';
import { RouterModule, Routes }    from '@angular/router';


import { PageNotFoundComponent }  from './page-not-found/page-not-found.component';


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


@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

相對(duì)導(dǎo)航

雖然構(gòu)建出了危機(jī)中心特性區(qū),你卻仍在使用以斜杠開(kāi)頭的絕對(duì)路徑來(lái)導(dǎo)航到危機(jī)詳情的路由。

路由器會(huì)從路由配置的頂層來(lái)匹配像這樣的絕對(duì)路徑。

你固然可以繼續(xù)像危機(jī)中心特性區(qū)一樣使用絕對(duì)路徑,但是那樣會(huì)把鏈接釘死在特定的父路由結(jié)構(gòu)上。 如果你修改了父路徑 "/crisis-center",那就不得不修改每一個(gè)鏈接參數(shù)數(shù)組。

通過(guò)改成定義相對(duì)于當(dāng)前 URL 的路徑,你可以把鏈接從這種依賴中解放出來(lái)。 當(dāng)你修改了該特性區(qū)的父路由路徑時(shí),該特性區(qū)內(nèi)部的導(dǎo)航仍然完好無(wú)損。

路由器支持在鏈接參數(shù)數(shù)組中使用“目錄式”語(yǔ)法來(lái)為查詢路由名提供幫助:

&./ 或 無(wú)前導(dǎo)斜線 形式是相對(duì)于當(dāng)前級(jí)別的。

&../ 會(huì)回到當(dāng)前路由路徑的上一級(jí)。

&你可以把相對(duì)導(dǎo)航語(yǔ)法和一個(gè)祖先路徑組合起來(lái)用。 如果不得不導(dǎo)航到一個(gè)兄弟路由,你可以用 ../<sibling& 來(lái)回到上一級(jí),然后進(jìn)入兄弟路由路徑中。

Router.navigate 方法導(dǎo)航到相對(duì)路徑時(shí),你必須提供當(dāng)前的 ActivatedRoute,來(lái)讓路由器知道你現(xiàn)在位于路由樹(shù)中的什么位置。

在鏈接參數(shù)數(shù)組后面,添加一個(gè)帶有 relativeTo 屬性的對(duì)象,并把它設(shè)置為當(dāng)前的 ActivatedRoute。 這樣路由器就會(huì)基于當(dāng)前激活路由的位置來(lái)計(jì)算出目標(biāo) URL。

當(dāng)調(diào)用路由器的 navigateByUrl() 時(shí),總是要指定完整的絕對(duì)路徑。

使用相對(duì) URL 導(dǎo)航到危機(jī)列表

你已經(jīng)注入了組成相對(duì)導(dǎo)航路徑所需的 ActivatedRoute

如果用 RouterLink 來(lái)代替 Router 服務(wù)進(jìn)行導(dǎo)航,就要使用相同的鏈接參數(shù)數(shù)組,不過(guò)不再需要提供 relativeTo 屬性。 ActivatedRoute已經(jīng)隱含在了RouterLink` 指令中。

修改 CrisisDetailComponentgotoCrises() 方法,來(lái)使用相對(duì)路徑返回危機(jī)中心列表。

Path:"src/app/crisis-center/crisis-detail/crisis-detail.component.ts (relative navigation)" 。

// Relative navigation back to the crises
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });

注意這個(gè)路徑使用了 ../ 語(yǔ)法返回上一級(jí)。 如果當(dāng)前危機(jī)的 id 是 3,那么最終返回到的路徑就是 "/crisis-center/;id=3;foo=foo"。

用命名出口(outlet)顯示多重路由

你決定給用戶提供一種方式來(lái)聯(lián)系危機(jī)中心。 當(dāng)用戶點(diǎn)擊“Contact”按鈕時(shí),你要在一個(gè)彈出框中顯示一條消息。

即使在應(yīng)用中的不同頁(yè)面之間切換,這個(gè)彈出框也應(yīng)該始終保持打開(kāi)狀態(tài),直到用戶發(fā)送了消息或者手動(dòng)取消。 顯然,你不能把這個(gè)彈出框跟其它放到頁(yè)面放到同一個(gè)路由出口中。

迄今為止,你只定義過(guò)單路由出口,并且在其中嵌套了子路由以便對(duì)路由分組。 在每個(gè)模板中,路由器只能支持一個(gè)無(wú)名主路由出口。

模板還可以有多個(gè)命名的路由出口。 每個(gè)命名出口都自己有一組帶組件的路由。 多重出口可以在同一時(shí)間根據(jù)不同的路由來(lái)顯示不同的內(nèi)容。

AppComponent 中添加一個(gè)名叫 “popup” 的出口,就在無(wú)名出口的下方。

Path:"src/app/app.component.html (outlets)" 。

<div [@routeAnimation]="getAnimationData(routerOutlet)">
  <router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>

一旦你學(xué)會(huì)了如何把一個(gè)彈出框組件路由到該出口,那里就是將會(huì)出現(xiàn)彈出框的地方。

  1. 第二路由。

命名出口是第二路由的目標(biāo)。

第二路由很像主路由,配置方式也一樣。它們只有一些關(guān)鍵的不同點(diǎn):

  • 它們彼此互不依賴。

  • 它們與其它路由組合使用。

  • 它們顯示在命名出口中。

生成一個(gè)新的組件來(lái)組合這個(gè)消息。

    ng generate component compose-message

它顯示一個(gè)簡(jiǎn)單的表單,包括一個(gè)頭、一個(gè)消息輸入框和兩個(gè)按鈕:“Send”和“Cancel”。

下面是該組件及其模板和樣式:

  • Path:"src/app/compose-message/compose-message.component.css" 。

        :host {
          position: relative; bottom: 10%;
        }

  • Path:"src/app/compose-message/compose-message.component.html" 。

        <h3>Contact Crisis Center</h3>
        <div *ngIf="details">
          {{ details }}
        </div>
        <div>
          <div>
            <label>Message: </label>
          </div>
          <div>
            <textarea [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea>
          </div>
        </div>
        <p *ngIf="!sending">
          <button (click)="send()">Send</button>
          <button (click)="cancel()">Cancel</button>
        </p>

  • Path:"src/app/compose-message/compose-message.component.ts" 。

        import { Component, HostBinding } from '@angular/core';
        import { Router }                 from '@angular/router';


        @Component({
          selector: 'app-compose-message',
          templateUrl: './compose-message.component.html',
          styleUrls: ['./compose-message.component.css']
        })
        export class ComposeMessageComponent {
          details: string;
          message: string;
          sending = false;


          constructor(private router: Router) {}


          send() {
            this.sending = true;
            this.details = 'Sending Message...';


            setTimeout(() => {
              this.sending = false;
              this.closePopup();
            }, 1000);
          }


          cancel() {
            this.closePopup();
          }


          closePopup() {
            // Providing a `null` value to the named outlet
            // clears the contents of the named outlet
            this.router.navigate([{ outlets: { popup: null }}]);
          }
        }

它看起來(lái)幾乎和你以前見(jiàn)過(guò)其它組件一樣,但有兩個(gè)值得注意的區(qū)別。

注意,send() 方法在發(fā)送消息和關(guān)閉彈出框之前通過(guò)等待模擬了一秒鐘的延遲。

closePopup() 方法用把 popup 出口導(dǎo)航到 null 的方式關(guān)閉了彈出框,它在稍后的部分有講解。

  1. 添加第二路由。

打開(kāi) AppRoutingModule,并把一個(gè)新的 compose 路由添加到 appRoutes 中。

Path:"src/app/app-routing.module.ts (compose route)" 。

    {
      path: 'compose',
      component: ComposeMessageComponent,
      outlet: 'popup'
    },

除了 pathcomponent 屬性之外還有一個(gè)新的屬性 outlet,它被設(shè)置成了 'popup'。 這個(gè)路由現(xiàn)在指向了 popup 出口,而 ComposeMessageComponent 也將顯示在那里。

為了給用戶某種途徑來(lái)打開(kāi)這個(gè)彈出框,還要往 AppComponent 模板中添加一個(gè)“Contact”鏈接。

Path:"src/app/app.component.html (contact-link)" 。

    <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

雖然 compose 路由被配置到了 popup 出口上,但這仍然不足以把該路由和 RouterLink 指令聯(lián)系起來(lái)。 你還要在鏈接參數(shù)數(shù)組中指定這個(gè)命名出口,并通過(guò)屬性綁定的形式把它綁定到 `RouterLink 上。

鏈接參數(shù)數(shù)組包含一個(gè)只有一個(gè) outlets 屬性的對(duì)象,它的值是另一個(gè)對(duì)象,這個(gè)對(duì)象以一個(gè)或多個(gè)路由的出口名作為屬性名。 在這里,它只有一個(gè)出口名“popup”,它的值則是另一個(gè)鏈接參數(shù)數(shù)組,用于指定 compose 路由。

換句話說(shuō),當(dāng)用戶點(diǎn)擊此鏈接時(shí),路由器會(huì)在路由出口 popup 中顯示與 compose 路由相關(guān)聯(lián)的組件。

當(dāng)只需要考慮一個(gè)路由和一個(gè)無(wú)名出口時(shí),外部對(duì)象中的這個(gè) outlets 對(duì)象是完全不必要的。

路由器假設(shè)這個(gè)路由指向了無(wú)名的主出口,并為你創(chuàng)建這些對(duì)象。

路由到一個(gè)命名出口會(huì)揭示一個(gè)路由特性: 你可以在同一個(gè) RouterLink 指令中為多個(gè)路由出口指定多個(gè)路由。

  1. 第二路由導(dǎo)航:在導(dǎo)航期間合并路由

導(dǎo)航到危機(jī)中心并點(diǎn)擊“Contact”,你將會(huì)在瀏覽器的地址欄看到如下 URL:

    http://.../crisis-center(popup:compose)

這個(gè) URL 中有意義的部分是 ... 后面的這些:

  • "crisis-center" 是主導(dǎo)航。

  • 圓括號(hào)包裹的部分是第二路由。

  • 第二路由包括一個(gè)出口名稱(popup)、一個(gè)冒號(hào)分隔符和第二路由的路徑(compose)。

點(diǎn)擊 Heroes 鏈接,并再次查看 URL

    http://.../heroes(popup:compose)

主導(dǎo)航的部分變化了,而第二路由沒(méi)有變。

路由器在導(dǎo)航樹(shù)中對(duì)兩個(gè)獨(dú)立的分支保持追蹤,并在 URL 中對(duì)這棵樹(shù)進(jìn)行表達(dá)。

你還可以添加更多出口和更多路由(無(wú)論是在頂層還是在嵌套的子層)來(lái)創(chuàng)建一個(gè)帶有多個(gè)分支的導(dǎo)航樹(shù)。 路由器將會(huì)生成相應(yīng)的 URL。

通過(guò)像前面那樣填充 outlets 對(duì)象,你可以告訴路由器立即導(dǎo)航到一棵完整的樹(shù)。 然后把這個(gè)對(duì)象通過(guò)一個(gè)鏈接參數(shù)數(shù)組傳給 router.navigate 方法。

  1. 清除第二路由。

像常規(guī)出口一樣,二級(jí)出口會(huì)一直存在,直到你導(dǎo)航到新組件。

每個(gè)第二出口都有自己獨(dú)立的導(dǎo)航,跟主出口的導(dǎo)航彼此獨(dú)立。 修改主出口中的當(dāng)前路由并不會(huì)影響到 popup 出口中的。 這就是為什么在危機(jī)中心和英雄管理之間導(dǎo)航時(shí),彈出框始終都是可見(jiàn)的。

再看 closePopup() 方法:

Path:"src/app/compose-message/compose-message.component.ts (closePopup)" 。

closePopup() {
  // Providing a `null` value to the named outlet
  // clears the contents of the named outlet
  this.router.navigate([{ outlets: { popup: null }}]);
}

單擊 “send” 或 “cancel” 按鈕可以清除彈出視圖。closePopup() 函數(shù)會(huì)使用 Router.navigate() 方法強(qiáng)制導(dǎo)航,并傳入一個(gè)鏈接參數(shù)數(shù)組。

就像在 AppComponent 中綁定到的 Contact RouterLink 一樣,它也包含了一個(gè)帶 outlets 屬性的對(duì)象。 outlets 屬性的值是另一個(gè)對(duì)象,該對(duì)象用一些出口名稱作為屬性名。 唯一的命名出口是 'popup'。

但這次,'popup' 的值是 null。null 不是一個(gè)路由,但卻是一個(gè)合法的值。 把 popup 這個(gè) RouterOutlet 設(shè)置為 null 會(huì)清除該出口,并且從當(dāng)前 URL 中移除第二路由 popup。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)