路由與導(dǎo)航
在用戶使用應(yīng)用程序時(shí),Angular 的路由器能讓用戶從一個(gè)視圖導(dǎo)航到另一個(gè)視圖。
概覽
Angular 的 Router(即“路由器”)把瀏覽器中的 URL 看做一個(gè)操作指南, 據(jù)此導(dǎo)航到一個(gè)視圖,并可以把參數(shù)傳給支撐視圖的相應(yīng)組件,幫它決定具體該展現(xiàn)哪些內(nèi)容。
路由器還在瀏覽器的歷史日志中記錄下這些活動,這樣瀏覽器的前進(jìn)和后退按鈕也能照常工作。
基礎(chǔ)知識
base href元素,來告訴路由器該如何合成導(dǎo)航用的 URL。
需要在index.html的 <head> 標(biāo)簽下先添加一個(gè) <base> 元素。
src/index.html
<base href="/">
從路由庫中導(dǎo)入
Angular 的路由器是一個(gè)可選的服務(wù),它用來呈現(xiàn)指定的 URL 所對應(yīng)的視圖。 它并不是 Angular 核心庫的一部分,而是在它自己的 @angular/router 包中。
src/app/app.module.ts
import { RouterModule, Routes } from '@angular/router';
配置
路由器需要先配置才會有路由信息。 下面的例子創(chuàng)建了五個(gè)路由定義,并用 <font size=5>RouterModule.forRoot</font> 方法來配置路由器, 并把它的返回值添加到 AppModule 的 imports 數(shù)組中。
src/app/app.module.ts
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'hero/:id', component: HeroDetailComponent },
{
path: 'heroes',
component: HeroListComponent,
data: { title: 'Heroes List' }
},
{
path:'portal',
loadChildren: './settings/settings.module#SettingsModule'
}
{ path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{ path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
// other imports here
],
...
})
export class AppModule { }
這里的路由數(shù)組 appRoutes 描述如何進(jìn)行導(dǎo)航。 把它傳給 RouterModule.forRoot 方法并傳給本模塊的 imports 數(shù)組就可以配置路由器。
每個(gè) Route 都會把一個(gè) URL 的 path 映射到一個(gè)組件。
注意,path 不能以斜杠(/)開頭,可以以(../)開頭。
第二個(gè)路由中的 :id 是一個(gè)路由參數(shù)的令牌(Token)。比如 /hero/42 這個(gè) URL 中,“42”就是 id 參數(shù)的值。
第三個(gè)路由中的 data 屬性用來存放于每個(gè)具體路由有關(guān)的任意信息。該數(shù)據(jù)可以被任何一個(gè)激活路由訪問,并能用來保存諸如 頁標(biāo)題、面包屑以及其它靜態(tài)只讀數(shù)據(jù)。
第四個(gè)路由中我們沒有將 ettingsModule導(dǎo)入到我們的 AppModule 中,而是通過 loadChildren 屬性來告訴 Angular 路由依據(jù) loadChildren 屬性配置的路徑去加載 SettingsModule 模塊。這就是模塊懶加載功能的具體應(yīng)用,當(dāng)用戶訪問 /settings/** 路徑的時(shí)候,才會加載對應(yīng)的 SettingsModule 模塊,這減少了應(yīng)用啟動時(shí)加載資源的大小。
另外我們傳遞一個(gè)字符串作為 loadChildren 的屬性值,該字符串由三部分組成:
(1)需要導(dǎo)入模塊的相對路徑
(2)# 分隔符
(3)導(dǎo)出模塊類的名稱
第五個(gè)路由中的空路徑('')表示應(yīng)用的默認(rèn)路徑,當(dāng) URL 為空時(shí)就會訪問那里,因此它通常會作為起點(diǎn)。 這個(gè)默認(rèn)路由會重定向到 URL /heroes,并顯示 HeroesListComponent。
最后一個(gè)路由中的 ** 路徑是一個(gè)通配符。當(dāng)所請求的 URL 不匹配前面定義的路由表中的任何路徑時(shí),路由器就會選擇此路由。 這個(gè)特性可用于顯示“404 - Not Found”頁,或自動重定向到其它路由。
這些路由的定義順序是刻意如此設(shè)計(jì)的。路由器使用先匹配者優(yōu)先的策略來匹配路由,所以,具體路由應(yīng)該放在通用路由的前面。在上面的配置中,帶靜態(tài)路徑的路由被放在了前面,后面是空路徑路由,因此它會作為默認(rèn)路由。而通配符路由被放在最后面,這是因?yàn)樗芷ヅ渖厦恳粋€(gè) URL,因此應(yīng)該只有在前面找不到其它能匹配的路由時(shí)才匹配它。
如果你想要看到在導(dǎo)航的生命周期中發(fā)生過哪些事件,可以使用路由器默認(rèn)配置中的 enableTracing 選項(xiàng)。它會把每個(gè)導(dǎo)航生命周期中的事件輸出到瀏覽器的控制臺。 這應(yīng)該只用于調(diào)試。你只需要把 enableTracing: true 選項(xiàng)作為第二個(gè)參數(shù)傳給 RouterModule.forRoot()方法就可以了。
路由數(shù)組
Routes是路由配置數(shù)組。每個(gè)都有以下屬性:
-
path是路由匹配的路徑。 -
pathMatch是指定匹配策略的字符串。pathMatch:'full'表示完全匹配 -
matcher定義了路徑匹配并取代自定義策略path和pathMatch。 -
component是組件類型。 -
redirectTo是將替換當(dāng)前匹配段的url片段。 -
outlet是組件應(yīng)放入的插座的名稱。 -
canActivate控制是否允許進(jìn)入路由。。 -
canActivateChild等同canActivate,只不過針對是所有子路由。。 -
canDeactivate控制是否允許離開路由。 -
canLoad控制是否允許延遲加載整個(gè)模塊。 -
data是提供給組件的附加數(shù)據(jù),被激活路由訪問。 -
resolve是用于查找數(shù)據(jù)解析器的DI令牌的映射。 -
children是子路由定義的數(shù)組。 -
loadChildren是對延遲加載子路由的引用。
注意:路由守衛(wèi)對于權(quán)限控制非常便利,當(dāng)然其粒度當(dāng)然只能在頁面層級。倘若需要對按鈕粒度也只能利用指令的方式,而二者的結(jié)合可以極大的改善權(quán)限控制埋點(diǎn)的代碼量。
RouterModule.forChild()
RouterModule.forChild() 與 Router.forRoot() 方法類似,但它只能應(yīng)用在特性模塊中。
- 友情提示:根模塊中使用 forRoot(),子模塊中使用 forChild()
這個(gè)功能非常強(qiáng)大,因?yàn)槲覀儾槐卦谝粋€(gè)地方(我們的主模塊)定義所有路由信息。反之,我們可以在特性模塊中定義模塊特有的路由信息,并在必要的時(shí)候?qū)⑺鼈儗?dǎo)入我們主模塊。RouterModule.forChild() 的使用方法如下:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
export const ROUTES: Routes = [
{
path: 'settings',
component: SettingsComponent,
///settings 設(shè)置頁面下有 /settings/profile 和 /settings/password 兩個(gè)頁面
children: [
{ path: 'profile', component: ProfileSettingsComponent },
{ path: 'password', component: PasswordSettingsComponent }
]
}
];
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(ROUTES)
],
// ...
})
export class ChildModule {}
通過以上示例,我們知道在主模塊和特性模塊中,路由配置對象的類型是一樣的,區(qū)別只是主模塊和特性模塊中需調(diào)用不同的方法,來配置模塊路由。
路由出口
RouterOutlet 是一個(gè)來自路由模塊中的指令,它的用法類似于組件。 它扮演一個(gè)占位符的角色,用于在模板中標(biāo)出一個(gè)位置,路由器將會把要顯示在這個(gè)出口處的組件顯示在這里。
<router-outlet></router-outlet>
<!-- Routed components go here -->
有了這份配置,當(dāng)本應(yīng)用在瀏覽器中的 URL 變?yōu)?/heroes 時(shí),路由器就會匹配到 path 為 heroes 的 Route,并在宿主視圖中的RouterOutlet之后顯示 HeroListComponent 組件。
- 多個(gè)路由區(qū)域
1.路由配置
const routes: Routes = [
{ path: 'news',
component: NewsComponent,
outlet:'let1'
}
{ path: 'news',
component: News2Cmponent,
outlet:'let2'
}]
2.html點(diǎn)擊鏈接
<a routerLink = "[{ outlets: { let1: ['news'] } }]"></a>
<a routerLink = "[{ outlets: { let2: ['news'] } }]"></a
3.html路由出口
<router-outlet name="let1"></router-outlet>
<router-outlet name="let2"></router-outlet>
即訪問 /news/ 時(shí)同時(shí)加載 NewsComponent 和 News2Cmponent 兩個(gè)組件
路由器鏈接
現(xiàn)在,你已經(jīng)有了配置好的一些路由,還找到了渲染它們的地方,但又該如何導(dǎo)航到它呢?固然,從瀏覽器的地址欄直接輸入 URL 也能做到,但是大多數(shù)情況下,導(dǎo)航是某些用戶操作的結(jié)果,比如點(diǎn)擊一個(gè) A 標(biāo)簽。
考慮下列模板:
src/app/app.component.html
<h1>Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
a 標(biāo)簽上的 <font size=5>RouterLink</font> 指令讓路由器得以控制這個(gè) a 元素。 這里的導(dǎo)航路徑是固定的,因此可以把一個(gè)字符串賦給 routerLink(“一次性”綁定)。
routerLink第一個(gè)路徑片段可以以 / ,./ 或 ../ 開頭:
如果以
/開頭,路由將從根路由開始查找如果以
./開頭或沒有使用/,則路由將從當(dāng)前激活路由的子路由開始查找如果以
../開頭,路由往上一級查找
如果需要更加動態(tài)的導(dǎo)航路徑,那就把它綁定到一個(gè)返回鏈接參數(shù)數(shù)組的模板表達(dá)式。 路由器會把這個(gè)數(shù)組解析成完整的 URL。
例如使用 ['/team', teamId, 'user', userName, {details: true}] 數(shù)組,意味著我們想要生成一個(gè)鏈接到 /team/11/user/bob;details=true 。
- ts中跳轉(zhuǎn)寫法
import { Router } from '@angular/router';
// ...
constructor(private router: Router) {}
// ...
this.router.navigate(['/detail', this.news.id])
this.router.navigate([{ outlets: { let2: null }}]);
navigateByUrl 方法指向完整的絕對路徑
路由鏈接的激活狀態(tài)
<a routerLink="/user/bob" routerLinkActive="active">Bob</a>
RouterLinkActive 指令:當(dāng) URL 地址是 /user 或 /user/bob 時(shí),當(dāng)前的 RouterState 為活動狀態(tài),active 類將會被添加到 <a> 標(biāo)簽上。如果 URL 發(fā)生變化,則 active類將自動從 <a> 標(biāo)簽上移除。
路由鏈接的激活狀態(tài)會向下級聯(lián)到路由樹中的每個(gè)層級,所以,父子路由鏈接可能會同時(shí)激活。
只有當(dāng) URL 與當(dāng)前 URL 精確匹配時(shí)才會激活,可以把 [routerLinkActiveOptions] 綁定為 { exact: true } 表達(dá)式。
路由器狀態(tài)
路由器的當(dāng)前狀態(tài)(RouterState):在導(dǎo)航時(shí)的每個(gè)生命周期成功完成時(shí),路由器會構(gòu)建出一個(gè) ActivatedRoute 組成的樹。
你可以在應(yīng)用中的任何地方用 Router 服務(wù)及其 routerState 屬性來訪問當(dāng)前的 RouterState 值。
RouterState 中的每個(gè) ActivatedRoute 都提供了從任意激活路由開始向上或向下遍歷路由樹的一種方式,以獲得關(guān)于父、子、兄弟路由的信息。
class MyComponent {
constructor(router: Router) {
const state: RouterState = router.routerState;
const snapshot: RouterStateSnapshot = state.snapshot;
const root: ActivatedRouteSnapshot = snapshot.root;
const child = root.firstChild;
const id: Observable<string> = child.params.map(p => p.id);
//...
}
}
激活的路由
該路由的路徑和參數(shù)可以通過注入進(jìn)來的一個(gè)名叫ActivatedRoute的路由服務(wù)來獲取。 它有一大堆有用的信息,包括:
| 屬性 | 說明 |
|---|---|
url |
路由路徑的 Observable 對象,是一個(gè)由路由路徑中的各個(gè)部分組成的字符串?dāng)?shù)組。 |
data |
一個(gè) Observable,其中包含提供給路由的 data 對象。也包含由解析守衛(wèi)(resolve guard)解析而來的值。 |
paramMap |
一個(gè) Observable,其中包含一個(gè)由當(dāng)前路由的必要參數(shù)和可選參數(shù)組成的map對象。用這個(gè) map可以獲取來自同名參數(shù)的單一值或多重值。 |
queryParamMap |
一個(gè) Observable,其中包含一個(gè)對所有路由都有效的查詢參數(shù)組成的map對象。 用這個(gè) map 可以獲取來自查詢參數(shù)的單一值或多重值。 |
fragment |
一個(gè)適用于所有路由的 URL 的 fragment(片段)的 Observable。 |
outlet |
要把該路由渲染到的 RouterOutlet 的名字。對于無名路由,它的路由名是 primary,而不是空串。 |
routeConfig |
用于該路由的路由配置信息,其中包含原始路徑。 |
parent |
當(dāng)該路由是一個(gè)子路由時(shí),表示該路由的父級 ActivatedRoute。 |
firstChild |
包含該路由的子路由列表中的第一個(gè) ActivatedRoute。 |
children |
包含當(dāng)前路由下所有已激活的子路由。 |
//獲取路由參數(shù)
private route: ActivatedRoute,
this.username = this.route
.queryParamMap
.pipe(map(params => this.username = params.username));
路由事件
在每次導(dǎo)航中,Router 都會通過 Router.events 屬性發(fā)布一些導(dǎo)航事件。這些事件的范圍涵蓋了從開始導(dǎo)航到結(jié)束導(dǎo)航之間的很多時(shí)間點(diǎn)。下表中列出了全部導(dǎo)航事件:
| 路由器事件 | 說明 |
|---|---|
NavigationStart |
本事件會在導(dǎo)航開始時(shí)觸發(fā)。 |
RouteConfigLoadStart |
本事件會在 Router 惰性加載 某個(gè)路由配置之前觸發(fā)。 |
RouteConfigLoadEnd |
本事件會在惰性加載了某個(gè)路由后觸發(fā)。 |
RoutesRecognized |
本事件會在路由器解析完 URL,并識別出了相應(yīng)的路由時(shí)觸發(fā) |
| `GuardsCheckStart |
本事件會在路由器開始Guard` 階段之前觸發(fā)。 |
ChildActivationStart |
本事件會在路由器開始激活路由的子路由時(shí)觸發(fā)。 |
ActivationStart |
本事件會在路由器開始激活某個(gè)路由時(shí)觸發(fā)。 |
GuardsCheckEnd |
本事件會在路由器成功完成了 Guard 階段時(shí)觸發(fā)。 |
ResolveStart |
本事件會在 Router 開始解析(Resolve)階段時(shí)觸發(fā)。 |
ResolveEnd |
本事件會在路由器成功完成了路由的解析(Resolve)階段時(shí)觸發(fā)。 |
ChildActivationEnd |
本事件會在路由器激活了路由的子路由時(shí)觸發(fā)。 |
ActivationEnd |
本事件會在路由器激活了某個(gè)路由時(shí)觸發(fā)。 |
NavigationEnd |
本事件會在導(dǎo)航成功結(jié)束之后觸發(fā)。 |
NavigationCancel |
本事件會在導(dǎo)航被取消之后觸發(fā)。 這可能是因?yàn)樵趯?dǎo)航期間某個(gè)路由守衛(wèi)返回了 false。 |
NavigationError |
這個(gè)事件會在導(dǎo)航由于意料之外的錯誤而失敗時(shí)觸發(fā)。 |
Scroll |
本事件代表一個(gè)滾動事件。 |
當(dāng)啟用了 enableTracing 選項(xiàng)時(shí),這些事件也同時(shí)會記錄到控制臺中。要想查看對路由導(dǎo)航事件進(jìn)行過濾的例子,請?jiān)L問 Angular 中的可觀察對象一章的路由器部分
路由守衛(wèi)
適用于后臺管理等需要登錄才能使用的模塊
- 創(chuàng)建一個(gè)認(rèn)證服務(wù)
// app/auth.service.ts
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthService implements CanActivate {
canActivate() {
// 這里判斷登錄狀態(tài), 返回 true 或 false
return true;
}
}
- 添加或修改路由配置
// app/app.router.ts
// 增加 CanActivate
import { CanActivate ... } from '@angular/router';
// 配置中增加 canActivate 如:
{ path: 'admin', canActivate:[AuthService] ... }
總結(jié)一下
該應(yīng)用有一個(gè)配置過的路由器。 外殼組件中有一個(gè) RouterOutlet,它能顯示路由器所生成的視圖。 它還有一些 RouterLink,用戶可以點(diǎn)擊它們,來通過路由器進(jìn)行導(dǎo)航。
下面是一些路由器中的關(guān)鍵詞匯及其含義:
| 路由器部件 | 含義 |
|---|---|
Router(路由器) |
為激活的 URL 顯示應(yīng)用組件。管理從一個(gè)組件到另一個(gè)組件的導(dǎo)航。ts->this.router.navigateByUrl("/protel")
|
RouterModule |
一個(gè)獨(dú)立的 Angular 模塊,用于提供所需的服務(wù)提供商,以及用來在應(yīng)用視圖之間進(jìn)行導(dǎo)航的指令。ts->RouterModule.forRoot(Routers數(shù)組,ExtraOptions對象)
|
Routes(路由數(shù)組) |
定義了一個(gè)路由數(shù)組,每一個(gè)都會把一個(gè) URL 路徑映射到一個(gè)組件。ts->[(path:'',componet: ***)]
|
Route(路由) |
定義路由器該如何根據(jù) URL 模式(pattern)來導(dǎo)航到組件。大多數(shù)路由都由路徑和組件類構(gòu)成。 |
RouterOutlet(路由出口) |
該指令(<router-outlet>)用來標(biāo)記出路由器該在哪里顯示視圖。 |
RouterLink(路由鏈接) |
這個(gè)指令把可點(diǎn)擊的 HTML 元素綁定到某個(gè)路由。點(diǎn)擊帶有 routerLink 指令(綁定到字符串或鏈接參數(shù)數(shù)組)的元素時(shí)就會觸發(fā)一次導(dǎo)航。html-><a [routerLink]="[./order]"></a>
|
RouterLinkActive(活動路由鏈接) |
當(dāng) HTML 元素上或元素內(nèi)的routerLink變?yōu)榧せ罨蚍羌せ顮顟B(tài)時(shí),該指令為這個(gè) HTML 元素添加或移除 CSS 類。html中
|
ActivatedRoute(激活的路由) |
為每個(gè)路由組件提供的一個(gè)服務(wù),它包含特定于路由的信息,比如路由參數(shù)、靜態(tài)數(shù)據(jù)、解析數(shù)據(jù)、全局查詢參數(shù)和全局碎片(fragment)。ts中
|
RouterState(路由器狀態(tài)) |
路由器的當(dāng)前狀態(tài)包含了一棵由程序中激活的路由構(gòu)成的樹。它包含一些用于遍歷路由樹的快捷方法。 |
| 鏈接參數(shù)數(shù)組 | 這個(gè)數(shù)組會被路由器解釋成一個(gè)路由操作指南。你可以把一個(gè)RouterLink綁定到該數(shù)組,或者把它作為參數(shù)傳給Router.navigate方法。 |
| 路由組件 | 一個(gè)帶有RouterOutlet的 Angular 組件,它根據(jù)路由器的導(dǎo)航來顯示相應(yīng)的視圖。 |