本里程碑涵蓋了以下內(nèi)容:
這個示例應(yīng)用在“英雄指南”教程的“服務(wù)”部分重新創(chuàng)建了英雄特性區(qū),并復(fù)用了Tour of Heroes: Services example code / 下載范例中的大部分代碼。
典型的應(yīng)用具有多個特性區(qū),每個特性區(qū)都專注于特定的業(yè)務(wù)用途并擁有自己的文件夾。
該部分將向你展示如何將應(yīng)用重構(gòu)為不同的特性模塊、將它們導(dǎo)入到主模塊中,并在它們之間導(dǎo)航。
遵循下列步驟:
heroes
?目錄下創(chuàng)建一個帶路由的 ?HeroesModule
?,并把它注冊到根模塊 ?AppModule
?中。ng generate module heroes/heroes --module app --flat --routing
app
?下占位用的 ?hero-list
? 目錄移到 ?heroes
?目錄中。heroes/heroes.component.html
? 的內(nèi)容復(fù)制到 ?hero-list.component.html
? 模板中。<h2>
? 加文字,改成 ?<h2>HEROES</h2>
?。<app-hero-detail>
? 組件。heroes/heroes.component.css
? 文件的內(nèi)容復(fù)制到 ?hero-list.component.css
? 文件中。heroes/heroes.component.ts
? 文件的內(nèi)容復(fù)制到 ?hero-list.component.ts
? 文件中。HeroListComponent
?。selector
?改為 ?app-hero-list
?。對于路由組件來說,這些選擇器不是必須的,因?yàn)檫@些組件是在渲染頁面時動態(tài)插入的,不過選擇器對于在 HTML 元素樹中標(biāo)記和選中它們是很有用的。
hero-detail
? 目錄中的 ?hero.ts
?、?hero.service.ts
? 和 ?mock-heroes.ts
? 文件復(fù)制到 ?heroes
?子目錄下。message.service.ts
? 文件復(fù)制到 ?src/app
? 目錄下。hero.service.ts
? 文件中修改導(dǎo)入 ?message.service
? 的相對路徑。HeroesModule
?的元數(shù)據(jù)。HeroDetailComponent
?和 ?HeroListComponent
?,并添加到 ?HeroesModule
?模塊的 ?declarations
?數(shù)組中。import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesRoutingModule } from './heroes-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
HeroesRoutingModule
],
declarations: [
HeroListComponent,
HeroDetailComponent
]
})
export class HeroesModule {}
英雄管理部分的文件結(jié)構(gòu)如下:
英雄特性區(qū)中有兩個相互協(xié)作的組件:英雄列表和英雄詳情。當(dāng)你導(dǎo)航到列表視圖時,它會獲取英雄列表并顯示出來。當(dāng)你點(diǎn)擊一個英雄時,詳細(xì)視圖就會顯示那個特定的英雄。
通過把所選英雄的 id 編碼進(jìn)路由的 URL 中,就能告訴詳情視圖該顯示哪個英雄。
從新位置 ?src/app/heroes/
? 目錄中導(dǎo)入英雄相關(guān)的組件,并定義兩個“英雄管理”路由。
現(xiàn)在,你有了 ?Heroes
?模塊的路由,還得在 ?RouterModule
?中把它們注冊給路由器,和 ?AppRoutingModule
?中的做法幾乎完全一樣,只有一項(xiàng)重要的差別。
在 ?AppRoutingModule
?中,你使用了靜態(tài)的 ?RouterModule.forRoot()
? 方法來注冊路由和全應(yīng)用級服務(wù)提供者。在特性模塊中你要改用 ?forChild()
? 靜態(tài)方法。
只在根模塊 ?
AppRoutingModule
?中調(diào)用 ?RouterModule.forRoot()
?(如果在 ?AppModule
?中注冊應(yīng)用的頂層路由,那就在 ?AppModule
?中調(diào)用)。在其它模塊中,你就必須調(diào)用 ?RouterModule.forChild
? 方法來注冊附屬路由。
修改后的 ?HeroesRoutingModule
?是這樣的:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent },
{ path: 'hero/:id', component: HeroDetailComponent }
];
@NgModule({
imports: [
RouterModule.forChild(heroesRoutes)
],
exports: [
RouterModule
]
})
export class HeroesRoutingModule { }
考慮為每個特性模塊提供自己的路由配置文件。雖然特性路由目前還很少,但即使在小型應(yīng)用中,路由也會變得越來越復(fù)雜。
英雄類的路由目前定義在兩個地方:?HeroesRoutingModule
?中(并最終給 ?HeroesModule
?)和 ?AppRoutingModule
?中。
由特性模塊提供的路由會被路由器再組合上它們所導(dǎo)入的模塊的路由。這讓你可以繼續(xù)定義特性路由模塊中的路由,而不用修改主路由配置。
移除 ?HeroListComponent
?的導(dǎo)入和來自 ?app-routing.module.ts
? 中的 ?/heroes
? 路由。
保留默認(rèn)路由和通配符路由,因?yàn)檫@些路由仍然要在應(yīng)用的頂層使用。
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
// import { HeroListComponent } from './hero-list/hero-list.component'; // <-- delete this line
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
// { path: 'heroes', component: HeroListComponent }, // <-- delete this line
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
因?yàn)?nbsp;?HeroesModule
?現(xiàn)在提供了 ?HeroListComponent
?,所以把它從 ?AppModule
?的 ?declarations
?數(shù)組中移除?,F(xiàn)在你已經(jīng)有了一個單獨(dú)的 ?HeroesModule
?,你可以用更多的組件和不同的路由來演進(jìn)英雄特性區(qū)。
經(jīng)過這些步驟,?AppModule
?變成了這樣:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HeroesModule,
AppRoutingModule
],
declarations: [
AppComponent,
CrisisListComponent,
PageNotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
請注意該模塊的 ?imports
?數(shù)組,?AppRoutingModule
?是最后一個,并且位于 ?HeroesModule
?之后。
imports: [
BrowserModule,
FormsModule,
HeroesModule,
AppRoutingModule
],
路由配置的順序很重要,因?yàn)槁酚善鲿邮艿谝粋€匹配上導(dǎo)航所要求的路徑的那個路由。
當(dāng)所有路由都在同一個 ?AppRoutingModule
?時,你要把默認(rèn)路由和通配符路由放在最后(這里是在 ?/heroes
? 路由后面), 這樣路由器才有機(jī)會匹配到 ?/heroes
? 路由,否則它就會先遇到并匹配上該通配符路由,并導(dǎo)航到“頁面未找到”路由。
每個路由模塊都會根據(jù)導(dǎo)入的順序把自己的路由配置追加進(jìn)去。如果你先列出了 ?AppRoutingModule
?,那么通配符路由就會被注冊在“英雄管理”路由之前。通配符路由(它匹配任意URL)將會攔截住每一個到“英雄管理”路由的導(dǎo)航,因此事實(shí)上屏蔽了所有“英雄管理”路由。
反轉(zhuǎn)路由模塊的導(dǎo)入順序,就會看到當(dāng)點(diǎn)擊英雄相關(guān)的鏈接時被導(dǎo)向了“頁面未找到”路由。
回到 ?HeroesRoutingModule
?并再次檢查這些路由定義。?HeroDetailComponent
?路由的路徑中帶有 ?:id
? 令牌。
{ path: 'hero/:id', component: HeroDetailComponent }
?:id
? 令牌會為路由參數(shù)在路徑中創(chuàng)建一個“空位”。在這里,這種配置會讓路由器把英雄的 ?id
?插入到那個“空位”中。
如果要告訴路由器導(dǎo)航到詳情組件,并讓它顯示“Magneta”,你會期望這個英雄的 ?id
?像這樣顯示在瀏覽器的 URL 中:
localhost:4200/hero/15
如果用戶把此 URL 輸入到瀏覽器的地址欄中,路由器就會識別出這種模式,同樣進(jìn)入“Magneta”的詳情視圖。
路由參數(shù):必須的還是可選的?
在這個場景下,把路由參數(shù)的令牌 ?:id
? 嵌入到路由定義的 ?path
?中是一個好主意,因?yàn)閷τ?nbsp;?HeroDetailComponent
?來說 ?id
?是必須的,而且路徑中的值 ?15
?已經(jīng)足夠把到“Magneta”的路由和到其它英雄的路由明確區(qū)分開。
然后導(dǎo)航到 ?HeroDetailComponent
?組件。在那里,你期望看到所選英雄的詳情,這需要兩部分信息:導(dǎo)航目標(biāo)和該英雄的 ?id
?。
因此,這個鏈接參數(shù)數(shù)組中有兩個條目:路由的路徑和一個用來指定所選英雄 ?id
?的路由參數(shù)。
<a [routerLink]="['/hero', hero.id]">
路由器從該數(shù)組中組合出了目標(biāo) URL:?localhost:3000/hero/15
?。
路由器從 URL 中解析出路由參數(shù)(?id:15
?),并通過 ActivatedRoute 服務(wù)來把它提供給 ?HeroDetailComponent
?組件。
從路由器(?router
?)包中導(dǎo)入 ?Router
?、?ActivatedRoute
?和 ?Params
?類。
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
這里導(dǎo)入 ?switchMap
?操作符是因?yàn)槟闵院髮幚砺酚蓞?shù)的可觀察對象 ?Observable
?。
import { switchMap } from 'rxjs/operators';
把這些服務(wù)作為私有變量添加到構(gòu)造函數(shù)中,以便 Angular 注入它們(讓它們對組件可見)。
constructor(
private route: ActivatedRoute,
private router: Router,
private service: HeroService
) {}
在 ?ngOnInit()
? 方法中,使用 ?ActivatedRoute
?服務(wù)來檢索路由的參數(shù),從參數(shù)中提取出英雄的 ?id
?,并檢索要顯示的英雄。
ngOnInit() {
this.hero$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')!))
);
}
當(dāng)這個 map 發(fā)生變化時,?paramMap
?會從更改后的參數(shù)中獲取 ?id
?參數(shù)。
然后,讓 ?HeroService
?去獲取具有該 ?id
?的英雄,并返回 ?HeroService
?請求的結(jié)果。
?switchMap
?操作符做了兩件事。它把 ?HeroService
?返回的 ?Observable<Hero>
? 拍平,并取消以前的未完成請求。當(dāng) ?HeroService
?仍在檢索舊的 ?id
?時,如果用戶使用新的 ?id
?重新導(dǎo)航到這個路由,?switchMap
?會放棄那個舊請求,并返回新 ?id
?的英雄。
?AsyncPipe
?處理這個可觀察的訂閱,而且該組件的 ?hero
?屬性也會用檢索到的英雄(重新)進(jìn)行設(shè)置。
?ParamMap
?API 的靈感來自 URLSearchParams接口。它提供了處理路由參數(shù) ( ?paramMap
?) 和查詢參數(shù) ( ?queryParamMap
?) 的參數(shù)訪問的方法。
成員 |
詳情 |
---|---|
has(name)
|
如果參數(shù)名位于參數(shù)列表中,就返回 |
get(name)
|
如果這個 map 中有參數(shù)名對應(yīng)的參數(shù)值(字符串),就返回它,否則返回 |
getAll(name)
|
如果這個 map 中有參數(shù)名對應(yīng)的值,就返回一個字符串?dāng)?shù)組,否則返回空數(shù)組。當(dāng)一個參數(shù)名可能對應(yīng)多個值的時候,請使用 |
keys
|
返回這個 map 中的所有參數(shù)名組成的字符串?dāng)?shù)組。 |
在這個例子中,你接收了路由參數(shù)的 ?Observable
?對象。這種寫法暗示著這些路由參數(shù)在該組件的生存期內(nèi)可能會變化。
默認(rèn)情況下,如果它沒有訪問過其它組件就導(dǎo)航到了同一個組件實(shí)例,那么路由器傾向于復(fù)用組件實(shí)例。如果復(fù)用,這些參數(shù)可以變化。
假設(shè)父組件的導(dǎo)航欄有“前進(jìn)”和“后退”按鈕,用來輪流顯示英雄列表中中英雄的詳情。每次點(diǎn)擊都會強(qiáng)制導(dǎo)航到帶前一個或后一個 ?id
?的 ?HeroDetailComponent
?組件。
你肯定不希望路由器先從 DOM 中移除當(dāng)前的 ?HeroDetailComponent
?實(shí)例,只是為了用下一個 ?id
?重新創(chuàng)建它,因?yàn)樗鼘⒅匦落秩疽晥D。為了更好的用戶體驗(yàn),路由器會復(fù)用同一個組件實(shí)例,而只是更新參數(shù)。
由于 ?ngOnInit()
? 在每個組件實(shí)例化時只會被調(diào)用一次,所以你可以使用 ?paramMap
?可觀察對象來檢測路由參數(shù)在同一個實(shí)例中何時發(fā)生了變化。
當(dāng)在組件中訂閱一個可觀察對象時,你通常總是要在組件銷毀時取消這個訂閱。
不過,?ActivatedRoute
?中的可觀察對象是一個例外,因?yàn)?nbsp;?ActivatedRoute
?及其可觀察對象與 ?Router
?本身是隔離的。?Router
?會在不再需要時銷毀這個路由組件,這意味著此組件的所有成員也都會銷毀,包括注入進(jìn)來的 ?ActivatedRoute
?以及那些對它的所有 ?Observable
?屬性的訂閱。
?Router
?不會 ?complete
??ActivatedRoute
?的任何 ?Observable
?,所以其 ?finalize
?或 ?complete
?代碼塊都不會運(yùn)行。如果你要在 ?finalize
?中做些什么處理,你仍然要在 ?ngOnDestroy
?中取消訂閱。如果你的 ?Observable
?型管道有某些代碼不希望在當(dāng)前組件被銷毀后運(yùn)行,仍然要主動取消訂閱。
本應(yīng)用不需要復(fù)用 ?HeroDetailComponent
?。用戶總是會先返回英雄列表,再選擇另一位英雄。所以,不存在從一個英雄詳情導(dǎo)航到另一個而不用經(jīng)過英雄列表的情況。這意味著路由器每次都會創(chuàng)建一個全新的 ?HeroDetailComponent
?實(shí)例。
假如你很確定這個 ?HeroDetailComponent
?實(shí)例永遠(yuǎn)不會被復(fù)用,你可以使用 ?snapshot
?。
?route.snapshot
? 提供了路由參數(shù)的初始值。你可以通過它來直接訪問參數(shù),而不用訂閱或者添加 Observable 的操作符,代碼如下:
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id')!;
this.hero$ = this.service.getHero(id);
}
用這種技術(shù),?
snapshot
?只會得到這些參數(shù)的初始值。如果路由器可能復(fù)用該組件,那么就該用 ?paramMap
?可觀察對象的方式。本教程的示例應(yīng)用中就用了 ?paramMap
?可觀察對象。
?HeroDetailComponent
?的 “Back” 按鈕使用了 ?gotoHeroes()
? 方法,該方法會強(qiáng)制導(dǎo)航回 ?HeroListComponent
?。
路由的 ?navigate()
? 方法同樣接受一個單條目的鏈接參數(shù)數(shù)組,你也可以把它綁定到 ?[routerLink]
? 指令上。它保存著到 ?HeroListComponent
?組件的路徑:
gotoHeroes() {
this.router.navigate(['/heroes']);
}
如果想導(dǎo)航到 ?HeroDetailComponent
?以對 id 為 ?15
?的英雄進(jìn)行查看并編輯,就要在路由的 URL 中使用路由參數(shù)來指定必要參數(shù)值。
localhost:4200/hero/15
你也能在路由請求中添加可選信息。比如,當(dāng)從 ?hero-detail.component.ts
? 返回到列表時,如果能自動選中剛剛查看過的英雄就好了。
當(dāng)從 ?HeroDetailComponent
?返回時,你可以會通過把正在查看的英雄的 ?id
?作為可選參數(shù)包含在 URL 中來實(shí)現(xiàn)這個特性。
可選信息還可以包含其它形式,比如:
name='wind_'
?。after='12/31/2015' & before='1/1/2017'
? - 沒有特定的順序 - ?before='1/1/2017' & after='12/31/2015'
? - 具有各種格式 - ?during='currentYear'
?。由于這些參數(shù)不適合用作 URL 路徑,因此可以使用可選參數(shù)在導(dǎo)航過程中傳遞任意復(fù)雜的信息??蛇x參數(shù)不參與模式匹配,因此在表達(dá)上提供了巨大的靈活性。
和必要參數(shù)一樣,路由器也支持通過可選參數(shù)導(dǎo)航。在你定義完必要參數(shù)之后,再通過一個獨(dú)立的對象來定義可選參數(shù)。
通常,對于必傳的值(比如用于區(qū)分兩個路由路徑的)使用必備參數(shù);當(dāng)這個值是可選的、復(fù)雜的或多值的時,使用可選參數(shù)。
當(dāng)導(dǎo)航到 ?HeroDetailComponent
?時,你可以在路由參數(shù)中指定一個所要編輯的英雄 ?id
?,只要把它作為鏈接參數(shù)數(shù)組中的第二個條目就可以了。
<a [routerLink]="['/hero', hero.id]">
路由器在導(dǎo)航 URL 中內(nèi)嵌了 ?id
?的值,這是因?yàn)槟惆阉靡粋€ ?:id
? 占位符當(dāng)做路由參數(shù)定義在了路由的 ?path
?中:
{ path: 'hero/:id', component: HeroDetailComponent }
當(dāng)用戶點(diǎn)擊后退按鈕時,?HeroDetailComponent
?構(gòu)造了另一個鏈接參數(shù)數(shù)組,可以用它導(dǎo)航回 ?HeroListComponent
?。
gotoHeroes() {
this.router.navigate(['/heroes']);
}
該數(shù)組缺少一個路由參數(shù),這是因?yàn)橐郧澳悴恍枰?nbsp;?HeroListComponent
?發(fā)送信息。
現(xiàn)在,使用導(dǎo)航請求發(fā)送當(dāng)前英雄的 ?id
?,以便 ?HeroListComponent
?在其列表中突出顯示該英雄。
傳送一個包含可選 ?id
?參數(shù)的對象。為了演示,這里還在對象中定義了一個沒用的額外參數(shù)(?foo
?),?HeroListComponent
?應(yīng)該忽略它。下面是修改過的導(dǎo)航語句:
gotoHeroes(hero: Hero) {
const heroId = hero ? hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
// Include a junk 'foo' property for fun.
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
}
該應(yīng)用仍然能工作。點(diǎn)擊“back”按鈕返回英雄列表視圖。
注意瀏覽器的地址欄。
它應(yīng)該是這樣的,不過也取決于你在哪里運(yùn)行它:
localhost:4200/heroes;id=15;foo=foo
?id
?的值像這樣出現(xiàn)在 URL 中(?;id=15;foo=foo
?),但不在 URL 的路徑部分?!癏eroes”路由的路徑部分并沒有定義 ?:id
?。
可選的路由參數(shù)沒有使用“?”和“&”符號分隔,因?yàn)樗鼈儗⒂迷?nbsp;URL 查詢字符串中。它們是用“;”分隔的。這是矩陣 URL標(biāo)記法。
Matrix URL 寫法首次提出是在1996 提案中,提出者是 Web 的奠基人:Tim Berners-Lee。
雖然 Matrix 寫法未曾進(jìn)入過 HTML 標(biāo)準(zhǔn),但它是合法的。而且在瀏覽器的路由系統(tǒng)中,它作為從父路由和子路由中單獨(dú)隔離出參數(shù)的方式而廣受歡迎。Angular 的路由器正是這樣一個路由系統(tǒng),并支持跨瀏覽器的 Matrix 寫法。
開發(fā)到現(xiàn)在,英雄列表還沒有變化。沒有突出顯示的英雄行。
?HeroListComponent
?需要添加使用這些參數(shù)的代碼。
以前,當(dāng)從 ?HeroListComponent
?導(dǎo)航到 ?HeroDetailComponent
?時,你通過 ?ActivatedRoute
?服務(wù)訂閱了路由參數(shù)這個 ?Observable
?,并讓它能用在 ?HeroDetailComponent
?中。你把該服務(wù)注入到了 ?HeroDetailComponent
?的構(gòu)造函數(shù)中。
這次,你要進(jìn)行反向?qū)Ш?,?nbsp;?HeroDetailComponent
?到 ?HeroListComponent
?。
首先,擴(kuò)展該路由的導(dǎo)入語句,以包含進(jìn) ?ActivatedRoute
?服務(wù)的類;
import { ActivatedRoute } from '@angular/router';
導(dǎo)入 ?switchMap
?操作符,在路由參數(shù)的 ?Observable
?對象上執(zhí)行操作。
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
在 ?HeroListComponent
?構(gòu)造函數(shù)中注入 ?ActivatedRoute
?。
export class HeroListComponent implements OnInit {
heroes$!: Observable<Hero[]>;
selectedId = 0;
constructor(
private service: HeroService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = parseInt(params.get('id')!, 10);
return this.service.getHeroes();
})
);
}
}
?ActivatedRoute.paramMap
? 屬性是一個路由參數(shù)的 ?Observable
?。當(dāng)用戶導(dǎo)航到這個組件時,paramMap 會發(fā)射一個新值,其中包含 ?id
?。在 ?ngOnInit()
? 中,你訂閱了這些值,設(shè)置到 ?selectedId
?,并獲取英雄數(shù)據(jù)。
用 CSS 類綁定更新模板,把它綁定到 ?isSelected
?方法上。 如果該方法返回 ?true
?,此綁定就會添加 CSS 類 ?selected
?,否則就移除它。 在 ?<li>
? 標(biāo)記中找到它,就像這樣:
<h2>Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes$ | async" [class.selected]="hero.id === selectedId">
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
</li>
</ul>
<button type="button" routerLink="/sidekicks">Go to sidekicks</button>
當(dāng)選中列表?xiàng)l目時,要添加一些樣式。
.heroes .selected a {
background-color: #d6e6f7;
}
.heroes .selected a:hover {
background-color: #bdd7f5;
}
當(dāng)用戶從英雄列表導(dǎo)航到英雄“Magneta”并返回時,“Magneta”看起來是選中的:
這個可選的 ?foo
?路由參數(shù)人畜無害,路由器會繼續(xù)忽略它。
在這一節(jié),你將為英雄詳情組件添加一些動畫。
首先導(dǎo)入 ?BrowserAnimationsModule
?,并添加到 ?imports
?數(shù)組中:
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
imports: [
BrowserAnimationsModule,
],
})
接下來,為指向 ?HeroListComponent
?和 ?HeroDetailComponent
?的路由定義添加一個 ?data
?對象。 轉(zhuǎn)場是基于 ?state
?的,你將使用來自路由的 ?animation
?數(shù)據(jù)為轉(zhuǎn)場提供一個有名字的動畫 ?state
?。
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent, data: { animation: 'heroes' } },
{ path: 'hero/:id', component: HeroDetailComponent, data: { animation: 'hero' } }
];
@NgModule({
imports: [
RouterModule.forChild(heroesRoutes)
],
exports: [
RouterModule
]
})
export class HeroesRoutingModule { }
在根目錄 ?src/app/
? 下創(chuàng)建一個 ?animations.ts
?。內(nèi)容如下:
import {
trigger, animateChild, group,
transition, animate, style, query
} from '@angular/animations';
// Routable animations
export const slideInAnimation =
trigger('routeAnimation', [
transition('heroes <=> hero', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
query(':enter', [
style({ left: '-100%'})
]),
query(':leave', animateChild()),
group([
query(':leave', [
animate('300ms ease-out', style({ left: '100%'}))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%'}))
])
]),
query(':enter', animateChild()),
])
]);
該文件做了如下工作:
slideInAnimation
?的常量,并把它設(shè)置為一個名叫 ?routeAnimation
?的動畫觸發(fā)器。heroes
?和 ?hero
?路由之間來回切換時,如果進(jìn)入(?:enter
?)應(yīng)用視圖則讓組件從屏幕的左側(cè)滑入,如果離開(?:leave
?)應(yīng)用視圖則讓組件從右側(cè)劃出。回到 ?AppComponent
?,從 ?@angular/router
? 包導(dǎo)入 ?RouterOutlet
?,并從 ?'./animations.ts
? 導(dǎo)入 ?slideInAnimation
?。
為包含 ?slideInAnimation
?的 ?@Component
? 元數(shù)據(jù)添加一個 ?animations
?數(shù)組。
import { ChildrenOutletContexts } from '@angular/router';
import { slideInAnimation } from './animations';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [ slideInAnimation ]
})
要想使用路由動畫,就要把 ?RouterOutlet
?包裝到一個元素中。再把 ?@routeAnimation
? 觸發(fā)器綁定到該元素上。
為了把 ?@routeAnimation
? 轉(zhuǎn)場轉(zhuǎn)場到指定的狀態(tài),你需要從 ?ActivatedRoute
?的 ?data
?中提供它。?RouterOutlet
?導(dǎo)出成了一個模板變量 ?outlet
?,這樣你就可以綁定一個到路由出口的引用了。這個例子中使用了一個 ?routerOutlet
?變量。
<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>
<div [@routeAnimation]="getAnimationData()">
<router-outlet></router-outlet>
</div>
?@routeAnimation
? 屬性使用所提供的 ?routerOutlet
?引用來綁定到 ?getAnimationData()
?,它會根據(jù)主路由所提供的 ?data
?對象返回動畫的屬性。?animation
?屬性會根據(jù)你在 ?animations.ts
? 中定義 ?slideInAnimation
?時使用的 ?transition
?名稱進(jìn)行匹配。
export class AppComponent {
constructor(private contexts: ChildrenOutletContexts) {}
getAnimationData() {
return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
}
}
如果在兩個路由之間切換,導(dǎo)航進(jìn)來時,?HeroDetailComponent
?和 ?HeroListComponent
?會從左側(cè)滑入;導(dǎo)航離開時將會從右側(cè)劃出。
本節(jié)包括以下內(nèi)容:
做完這些修改之后,目錄結(jié)構(gòu)如下:
這里是當(dāng)前版本的范例程序相關(guān)文件。
import {
trigger, animateChild, group,
transition, animate, style, query
} from '@angular/animations';
// Routable animations
export const slideInAnimation =
trigger('routeAnimation', [
transition('heroes <=> hero', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
query(':enter', [
style({ left: '-100%'})
]),
query(':leave', animateChild()),
group([
query(':leave', [
animate('300ms ease-out', style({ left: '100%'}))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%'}))
])
]),
query(':enter', animateChild()),
])
]);
<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>
<div [@routeAnimation]="getAnimationData()">
<router-outlet></router-outlet>
</div>
import { Component } from '@angular/core';
import { ChildrenOutletContexts } from '@angular/router';
import { slideInAnimation } from './animations';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [ slideInAnimation ]
})
export class AppComponent {
constructor(private contexts: ChildrenOutletContexts) {}
getAnimationData() {
return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
}
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
HeroesModule,
AppRoutingModule
],
declarations: [
AppComponent,
CrisisListComponent,
PageNotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
/* . . . */
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
/* . . . */
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
/* HeroListComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 100%;
}
.heroes li {
position: relative;
cursor: pointer;
}
.heroes li:hover {
left: .1em;
}
.heroes a {
color: black;
text-decoration: none;
display: block;
font-size: 1.2rem;
background-color: #eee;
margin: .5rem .5rem .5rem 0;
padding: .5rem 0;
border-radius: 4px;
}
.heroes a:hover {
color: #2c3a41;
background-color: #e6e6e6;
}
.heroes a:active {
background-color: #525252;
color: #fafafa;
}
.heroes .selected a {
background-color: #d6e6f7;
}
.heroes .selected a:hover {
background-color: #bdd7f5;
}
.heroes .badge {
padding: .5em .6em;
color: white;
background-color: #435b60;
min-width: 16px;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
<h2>Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes$ | async" [class.selected]="hero.id === selectedId">
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
</li>
</ul>
<button type="button" routerLink="/sidekicks">Go to sidekicks</button>
// TODO: Feature Componetized like CrisisCenter
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HeroService } from '../hero.service';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroes$!: Observable<Hero[]>;
selectedId = 0;
constructor(
private service: HeroService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.heroes$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = parseInt(params.get('id')!, 10);
return this.service.getHeroes();
})
);
}
}
<h2>Heroes</h2>
<div *ngIf="hero$ | async as hero">
<h3>{{ hero.name }}</h3>
<p>Id: {{ hero.id }}</p>
<label for="hero-name">Hero name: </label>
<input type="text" id="hero-name" [(ngModel)]="hero.name" placeholder="name"/>
<button type="button" (click)="gotoHeroes(hero)">Back</button>
</div>
import { switchMap } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { HeroService } from '../hero.service';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
hero$!: Observable<Hero>;
constructor(
private route: ActivatedRoute,
private router: Router,
private service: HeroService
) {}
ngOnInit() {
this.hero$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')!))
);
}
gotoHeroes(hero: Hero) {
const heroId = hero ? hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
// Include a junk 'foo' property for fun.
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
}
}
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from '../message.service';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
getHero(id: number | string) {
return this.getHeroes().pipe(
// (+) before `id` turns the string into a number
map((heroes: Hero[]) => heroes.find(hero => hero.id === +id)!)
);
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesRoutingModule } from './heroes-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
HeroesRoutingModule
],
declarations: [
HeroListComponent,
HeroDetailComponent
]
})
export class HeroesModule {}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent, data: { animation: 'heroes' } },
{ path: 'hero/:id', component: HeroDetailComponent, data: { animation: 'hero' } }
];
@NgModule({
imports: [
RouterModule.forChild(heroesRoutes)
],
exports: [
RouterModule
]
})
export class HeroesRoutingModule { }
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
更多建議: