本節(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
? 文件夾中crisis-center.module.ts
? 和 ?crisis-center-routing.module.ts
?使用 mock 的 crises 來代替 mock 的 heroes:
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)分離(Separation of Concerns)原則,對(duì)危機(jī)中心的修改不會(huì)影響 ?
AppModule
?或其它特性模塊中的組件。
本節(jié)會(huì)展示如何組織危機(jī)中心,來滿足 Angular 應(yīng)用所推薦的模式:
如果你的應(yīng)用具有多個(gè)特性區(qū),那些特性的組件樹可能由多個(gè)組件構(gòu)成,每個(gè)都包含一些其它相關(guān)組件的分支。
在 ?crisis-center
? 目錄下生成一個(gè) ?CrisisCenter
?組件:
ng generate component crisis-center/crisis-center
使用如下代碼更新組件模板:
<h2>Crisis Center</h2>
<router-outlet></router-outlet>
?CrisisCenterComponent
?和 ?AppComponent
?有下列共同點(diǎn):
AppComponent
?是整個(gè)應(yīng)用的根AppComponent
?是管理高層工作流的殼就像大多數(shù)的殼一樣,?CrisisCenterComponent
?類是最小化的,因?yàn)樗鼪]有業(yè)務(wù)邏輯,它的模板中沒有鏈接,只有一個(gè)標(biāo)題和用于放置危機(jī)中心的子組件的 ?<router-outlet>
?。
在 ?crisis-center
? 目錄下生成一個(gè) ?CrisisCenterHome
?組件,作為 "危機(jī)中心" 特性的宿主頁面。
ng generate component crisis-center/crisis-center-home
用一條歡迎信息修改 ?Crisis Center
? 中的模板。
<h3>Welcome to the Crisis Center</h3>
把 ?heroes-routing.module.ts
? 文件復(fù)制過來,改名為 ?crisis-center-routing.module.ts
?,并修改它。這次你要把子路由定義在父路由 ?crisis-center
? 中。
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è)子組件:?CrisisCenterHomeComponent
?和 ?CrisisDetailComponent
?。
對(duì)這些子路由的處理中有一些重要的差異。
路由器會(huì)把這些路由對(duì)應(yīng)的組件放在 ?CrisisCenterComponent
?的 ?RouterOutlet
?中,而不是 ?AppComponent
?殼組件中的。
?CrisisListComponent
?包含危機(jī)列表和一個(gè) ?RouterOutlet
?,用以顯示 ?Crisis Center Home
? 和 ?Crisis 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)建該組件。
在頂層,以 ?/
? 開頭的路徑指向的總是應(yīng)用的根。但這里是子路由。它們是在父路由路徑的基礎(chǔ)上做出的擴(kuò)展。在路由樹中每深入一步,你就會(huì)在該路由的路徑上添加一個(gè)斜線 ?/
?(除非該路由的路徑是空的)。
如果把該邏輯應(yīng)用到危機(jī)中心中的導(dǎo)航,那么父路徑就是 ?/crisis-center
?。
CrisisCenterHomeComponent
?,完整的 URL 是 ?/crisis-center
? (?/crisis-center
? + ?''
? + ?''
?)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)入語句。
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 { }
就像 ?HeroesModule
?模塊中一樣,你必須把 ?CrisisCenterModule
?添加到 ?AppModule
?的 ?imports
?數(shù)組中,就在 ?AppRoutingModule
?前面:
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 {}
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 { }
這些模塊的導(dǎo)入順序是至關(guān)重要的,因?yàn)檫@些模塊中定義的路由的順序會(huì)影響路由的匹配順序。如果先導(dǎo)入 ?
AppModule
?,它的通配符路由 (?path: '**'
?)。
從 ?app.routing.ts
? 中移除危機(jī)中心的初始路由。因?yàn)楝F(xiàn)在是 ?HeroesModule
?和 ?CrisisCenter
?模塊提供了這些特性路由。
?app-routing.module.ts
? 文件中只有應(yīng)用的頂層路由,比如默認(rèn)路由和通配符路由。
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 {}
雖然構(gòu)建出了危機(jī)中心特性區(qū),你卻仍在使用以斜杠開頭的絕對(duì)路徑來導(dǎo)航到危機(jī)詳情的路由。
路由器會(huì)從路由配置的頂層來匹配像這樣的絕對(duì)路徑。
你固然可以繼續(xù)像危機(jī)中心特性區(qū)一樣使用絕對(duì)路徑,但是那樣會(huì)把鏈接釘死在特定的父路由結(jié)構(gòu)上。如果你修改了父路徑 ?/crisis-center
?,那就不得不修改每一個(gè)鏈接參數(shù)數(shù)組。
通過改成定義相對(duì)于當(dāng)前 URL 的路徑,你可以把鏈接從這種依賴中解放出來。當(dāng)你修改了該特性區(qū)的父路由路徑時(shí),該特性區(qū)內(nèi)部的導(dǎo)航仍然完好無損。
路由器支持在鏈接參數(shù)數(shù)組中使用“目錄式”語法來為查詢路由名提供幫助:你可以把相對(duì)導(dǎo)航語法和一個(gè)祖先路徑組合起來用。如果不得不導(dǎo)航到一個(gè)兄弟路由,你可以用 ?
目錄式語法
詳情
./
無前導(dǎo)斜線
形式是相對(duì)于當(dāng)前級(jí)別的。
../
回到當(dāng)前路由路徑的上一級(jí)。
../<sibling>
? 來回到上一級(jí),然后進(jìn)入兄弟路由路徑中。
用 ?Router.navigate
? 方法導(dǎo)航到相對(duì)路徑時(shí),你必須提供當(dāng)前的 ?ActivatedRoute
?,來讓路由器知道你現(xiàn)在位于路由樹中的什么位置。
在鏈接參數(shù)數(shù)組后面,添加一個(gè)帶有 ?relativeTo
?屬性的對(duì)象,并把它設(shè)置為當(dāng)前的 ?ActivatedRoute
?。這樣路由器就會(huì)基于當(dāng)前激活路由的位置來計(jì)算出目標(biāo) URL。
當(dāng)調(diào)用路由器的 ?
navigateByUrl()
? 時(shí),總是要指定完整的絕對(duì)路徑。
你已經(jīng)注入了組成相對(duì)導(dǎo)航路徑所需的 ?ActivatedRoute
?。
如果用 ?RouterLink
?來代替 ?Router
?服務(wù)進(jìn)行導(dǎo)航,就要使用相同的鏈接參數(shù)數(shù)組,不過不再需要提供 ?relativeTo
?屬性。?ActivatedRoute
?已經(jīng)隱含在了 ?RouterLink
?指令中。
修改 ?CrisisDetailComponent
?的 ?gotoCrises()
? 方法,來使用相對(duì)路徑返回危機(jī)中心列表。
// Relative navigation back to the crises
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
注意這個(gè)路徑使用了 ?../
? 語法返回上一級(jí)。如果當(dāng)前危機(jī)的 ?id
?是 ?3
?,那么最終返回到的路徑就是 ?/crisis-center/;id=3;foo=foo
?。
你決定給用戶提供一種方式來聯(lián)系危機(jī)中心。當(dāng)用戶點(diǎn)擊“Contact”按鈕時(shí),你要在一個(gè)彈出框中顯示一條消息。
即使在應(yīng)用中的不同頁面之間切換,這個(gè)彈出框也應(yīng)該始終保持打開狀態(tài),直到用戶發(fā)送了消息或者手動(dòng)取消。顯然,你不能把這個(gè)彈出框跟其它放到頁面放到同一個(gè)路由出口中。
迄今為止,你只定義過單路由出口,并且在其中嵌套了子路由以便對(duì)路由分組。在每個(gè)模板中,路由器只能支持一個(gè)無名主路由出口。
模板還可以有多個(gè)命名的路由出口。每個(gè)命名出口都自己有一組帶組件的路由。多重出口可以在同一時(shí)間根據(jù)不同的路由來顯示不同的內(nèi)容。
在 ?AppComponent
?中添加一個(gè)名叫“popup”的出口,就在無名出口的下方。
<div [@routeAnimation]="getAnimationData()">
<router-outlet></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
一旦你學(xué)會(huì)了如何把一個(gè)彈出框組件路由到該出口,那里就是將會(huì)出現(xiàn)彈出框的地方。
命名出口是第二路由的目標(biāo)。
第二路由很像主路由,配置方式也一樣。它們只有一些關(guān)鍵的不同點(diǎn)。
生成一個(gè)新的組件來組合這個(gè)消息。
ng generate component compose-message
它顯示一個(gè)簡(jiǎn)單的表單,包括一個(gè)頭、一個(gè)消息輸入框和兩個(gè)按鈕:“Send”和“Cancel”。
下面是該組件及其模板和樣式:
<h3>Contact Crisis Center</h3>
<div *ngIf="details">
{{ details }}
</div>
<div>
<div>
<label for="message">Enter your message: </label>
</div>
<div>
<textarea id="message" [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea>
</div>
</div>
<p *ngIf="!sending">
<button type="button" (click)="send()">Send</button>
<button type="button" (click)="cancel()">Cancel</button>
</p>
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 = '';
message = '';
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 }}]);
}
}
textarea {
width: 100%;
margin-top: 1rem;
font-size: 1.2rem;
box-sizing: border-box;
}
它看起來幾乎和你以前見過其它組件一樣,但有兩個(gè)值得注意的區(qū)別。
注意:
?send()
? 方法通過在“發(fā)送”消息之前等待一秒并關(guān)閉彈出窗口來模擬延遲。
?closePopup()
? 方法用把 ?popup
?出口導(dǎo)航到 ?null
?的方式關(guān)閉了彈出框,它在稍后的部分有講解。
打開 ?AppRoutingModule
?,并把一個(gè)新的 ?compose
?路由添加到 ?appRoutes
?中。
{
path: 'compose',
component: ComposeMessageComponent,
outlet: 'popup'
},
除了 ?path
?和 ?component
?屬性之外還有一個(gè)新的屬性 ?outlet
?,它被設(shè)置成了 ?'popup'
?。這個(gè)路由現(xiàn)在指向了 ?popup
?出口,而 ?ComposeMessageComponent
?也將顯示在那里。
為了給用戶某種途徑來打開這個(gè)彈出框,還要往 ?AppComponent
?模板中添加一個(gè)“Contact”鏈接。
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
雖然 ?compose
?路由被配置到了 ?popup
?出口上,但這仍然不足以把該路由和 ?RouterLink
?指令聯(lián)系起來。你還要在鏈接參數(shù)數(shù)組中指定這個(gè)命名出口,并通過屬性綁定的形式把它綁定到 ?RouterLink
?上。
鏈接參數(shù)數(shù)組包含一個(gè)只有一個(gè) ?outlets
?屬性的對(duì)象,它的值是另一個(gè)對(duì)象,這個(gè)對(duì)象以一個(gè)或多個(gè)路由的出口名作為屬性名。在這里,它只有一個(gè)出口名“popup”,它的值則是另一個(gè)鏈接參數(shù)數(shù)組,用于指定 ?compose
?路由。
換句話說,當(dāng)用戶點(diǎn)擊此鏈接時(shí),路由器會(huì)在路由出口 ?popup
?中顯示與 ?compose
?路由相關(guān)聯(lián)的組件。
當(dāng)只需要考慮一個(gè)路由和一個(gè)無名出口時(shí),外部對(duì)象中的這個(gè) ?outlets
?對(duì)象是完全不必要的。
路由器假設(shè)這個(gè)路由指向了無名的主出口,并為你創(chuàng)建這些對(duì)象。
路由到一個(gè)命名出口會(huì)揭示一個(gè)路由特性:你可以在同一個(gè) ?RouterLink
?指令中為多個(gè)路由出口指定多個(gè)路由。
導(dǎo)航到危機(jī)中心并點(diǎn)擊“Contact”,你將會(huì)在瀏覽器的地址欄看到如下 URL。
http://…/crisis-center(popup:compose)
這個(gè) URL 中有意義的部分是 ?...
? 后面的這些:
crisis-center
? 是主導(dǎo)航。
popup
?)、一個(gè)冒號(hào)分隔符和第二路由的路徑(?compose
?)。
點(diǎn)擊 Heroes 鏈接,并再次查看 URL。
http://…/heroes(popup:compose)
主導(dǎo)航的部分變化了,而第二路由沒有變。
路由器在導(dǎo)航樹中對(duì)兩個(gè)獨(dú)立的分支保持追蹤,并在 URL 中對(duì)這棵樹進(jìn)行表達(dá)。
你還可以添加更多出口和更多路由(無論是在頂層還是在嵌套的子層)來創(chuàng)建一個(gè)帶有多個(gè)分支的導(dǎo)航樹。路由器將會(huì)生成相應(yīng)的 URL。
通過像前面那樣填充 ?outlets
?對(duì)象,你可以告訴路由器立即導(dǎo)航到一棵完整的樹。然后把這個(gè)對(duì)象通過一個(gè)鏈接參數(shù)數(shù)組傳給 ?router.navigate
? 方法。
像常規(guī)出口一樣,二級(jí)出口會(huì)一直存在,直到你導(dǎo)航到新組件。
每個(gè)第二出口都有自己獨(dú)立的導(dǎo)航,跟主出口的導(dǎo)航彼此獨(dú)立。修改主出口中的當(dāng)前路由并不會(huì)影響到 ?popup
?出口中的。這就是為什么在危機(jī)中心和英雄管理之間導(dǎo)航時(shí),彈出框始終都是可見的。
再看 ?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
?。
更多建議: