最近在使用angular來作項目,今天就簡單的聊聊angular的路由管理。javascript
不管是angular仍是其它兩個主流框架,總體上就是一個大的組件樹,包括一些能夠複用的UI組件或者業務組件。而路由的做用就是關於如何使用分配這些組件。它來以爲在某種狀況下該顯示哪些組件,隱藏哪些組件。 咱們來看一個angular的路由配置:前端
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
children: [
{
path: 'persons',
children: [
{
path: 'list',
component:PersonListCom,
}
]
},
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
component: PersonDetailCom
}
]
},
{
path: '/', redirectTo: '/persons/list', pathMatch: 'full'
},
]
},
{
path: 'login',
component: LoginComponent,
}
];
複製代碼
Route對象定義應用程序中某些可路由狀態(組件、重定向等)與URL段之間的關係。經過導入RouterModule
並將路由配置數組傳遞給RouterModule.forRoot()
在應用程序中聲明性地指定路由器配置。java
你們能夠先不看具體代碼,我將路由的配置結構轉成了下面這張圖。 web
整個路由配置就是一個組件樹,每一個節點表明一個路由,從圖中能夠看出,有的節點和組件相關聯,在訪問這個路由時,對應的組件就會加載展現在屏幕上。咱們在訪問路由的時候,就是訪問組件樹的某條子樹。好比,我想要訪問PersonListCom
組件,那就是home/persons/list
的路由訪問順序,路由節點對應的組件就會進行實例化。 數組
home
路徑,下面有兩條子樹,它們沒法同時渲染,由於從標準上來講,
outlet
就是指組件具體放置在DOM中的位置,因此同一個位置只能放一個組件。因此咱們添加了
redirect
配置,在沒法訪問的時候,進行重定向。
路由的一個重要的功能就是在不一樣的路由狀態之間進行切換,而且更新組件的實例。 從路由訪問部分能夠看出,要訪問某一個路由節點,就是將某條子樹的節點串聯起來,好比:home/persons/list
,從另外一個角度來講,這條節點串就是咱們常說的URL,即,URL就是路由的扁平化標識。 而導航的做用就是從一個路由到另外一個路由狀態的切換。咱們在訪問不一樣頁面的時候,主要就是修改瀏覽器中的URL地址,框架會分析新的路由,隱藏舊的組件,並將新的組件渲染出來加載到頁面上。 因此路由器只容許咱們表達應用程序可能處於的全部潛在狀態,並提供一種從一種狀態導航到另外一種狀態的機制。 與原生應用程序相比,URL欄爲web應用程序提供了巨大的優點。它容許咱們引用狀態,爲它們添加書籤,並與咱們的朋友共享它們。在性能良好的web應用程序中,任何應用程序狀態轉換都會致使URL更改,而任何URL更改都會致使狀態轉換。換句話說,URL只不過是序列化的路由器狀態。angular路由器負責管理URL,以確保它始終與路由器狀態同步。瀏覽器
路由器的核心是一個強大的URL匹配引擎。若是不能將url與要呈現的適當組件集相關聯,則沒法在應用程序中導航。因此咱們須要瞭解整個URL的訪問過程 好比咱們要訪問: home/single/personDetail/23
bash
重定向:重定向是URL部分的替換。重定向能夠是本地的,也能夠是絕對的。本地重定向用不一樣的段替換單個段。絕對重定向替換整個URL。重定向是本地的,除非您在url前面加上斜線。數據結構
當用戶點擊跳轉時,URL地址會發生變化,router就會檢測到這種變化,首先要處理的就是是否須要重定向。重定向能夠在路由器配置樹中的每一個嵌套級別發生,但每一個級別只能發生一次。這是爲了不任何無限的重定向循環。 在前面的配置中有一個重定向的配置path: '/', redirectTo: '/persons/list', pathMatch: 'full'
,使用/persons/list
替換 /
,這就是一種絕對替換。 咱們要訪問home/persons/list
,不是/
因此不會發生重定向。框架
下一步就是路由狀態的識別。路由會從URL中獲取到路由狀態,而後路由器一個接一個地遍歷路由配置數組,檢查URL是否以路由的路徑開始。若是沒法匹配到正確的路由,導航跳轉就會失敗。但若是它完成匹配,表明應用會構造出將要跳轉頁面的路由器狀態。async
路由器狀態由激活的路由組成。每一個激活的路由能夠與一個組件相關聯。另外,請注意,咱們老是有一個激活的路由與應用程序的根組件相關聯。
在這個階段,我已經有了將要跳轉頁面的路由狀態。接下來就要經過路由守衛來檢測是否容許完成此次跳轉。
import { HomeGuard } from './page/home/home.guard';
{
path: 'home',
canLoad: [HomeGuard],
canActivate: [HomeGuard],
canActivateChild: [HomeGuard],
}
複製代碼
能夠看到這部分代碼,有canLoad
、canActivateChild
、canActivate
幾個節點,表示在路由執行的這幾個節點都須要執行HomeGuard文件中的判斷方法來檢測是否容許執行下面的步驟。若是全部守衛都返回 true,就會繼續導航。若是任何一個守衛返回了 false,就會取消導航。 HomeGuard中包括一下幾個接口,對應不一樣階段的處理。
/** * 決定該路由可否激活 **/
canActivate(){
}
/** * 來決定該路由的子路由可否激活 **/
canActivateChild() {
}
/** * 是否可加載模塊 */
canLoad(){
}
複製代碼
在經過路由守衛以後,就能夠處理數據了。在這個路徑中,23
是要訪問的用戶id,咱們須要經過這個id參數獲取用戶的數據。 在平常業務中,有時候只須要在組件加載完成以後,組件本身獲取到URL中的參數,請求數據就能夠了。但有些時候,須要在頁面加載以前去獲取用戶數據,若是有數據,就將數據傳給組件去渲染,若是沒有就中止加載頁面。
[
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
component: PersonDetailCom,
resolve: {
conversations: ConversationsResolver
}
}
]
}
]
複製代碼
在下面,咱們定義了ConversationsResolver
。
@Injectable()
class ConversationsResolver implements Resolve<any> {
constructor(
private repo: ConversationsRepo,
private currentPerson: Person) {}
resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot):
Promise<Conversation[]> {
return this.repo.fetchAll(
route.paramMap.get('id'),
this.currentPerson
);
}
}
複製代碼
當導航到single/personDetail/23
的時候,會獲取到當前路由的狀態,包括參數id:
23。使用這個參數請求用戶的Conversation數據。 而後在組件PersonDetailCom
中,能夠獲取到resolver中請求獲取到的數據。
@Component({
template: ` <conversation *ngFor="let c of conversations | async"> </conversation> `
})
class PersonDetailCom {
conversations: Observable<Conversation[]>;
constructor(route: ActivatedRoute) {
this.conversations = route.data.pluck('conversations');
}
}
複製代碼
在這個階段,咱們經過已經獲取到的路由狀態實例化須要的組件,將它放到對應的router-outlet
。 下面看下怎麼使用router-outlet
,咱們在HomeComponent中添加兩個outlets:默認的和header
。
@Component({ template: ` ... <router-outlet name="header"></router-outlet> ... <router-outlet></router-outlet> `
})
class HomeComponent { }
複製代碼
咱們能夠在路由配置中指定組件渲染的outlet。
[
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
children: [
{ path: '', component: PersonDetailCom },
{
path: '',
component: HeaderCom,
outlet: 'header',
}
],
resolve: {
conversations: ConversationsResolver
}
}
]
}
]
複製代碼
在路由personDetail/:id
下經過children添加了兩個組件PersonDetailCom
和HeaderCom
,而且HeaderCom
指定了outlet
是header
。 這樣,PersonDetailCom
組件實例化以後會被放在默認的outelet上,HeaderCom
會被放置在header上。
除了配置resolver
以外,還能夠在組件中獲取參數id,請求對應數據。
@Component({...})
class PersonDetailCom {
conversation: Observable<Conversation>;
id: Observable<string>;
constructor(r: ActivatedRoute) {
// r.data is an observable
this.conversation = r.data.map(d => d.conversation);
// r.paramMap is an observable
this.id = r.paramMap.map(p => p.get('id')); }
}
複製代碼
經過ActivatedRoute
能夠獲取到URL上的參數,經過參數獲取數據。
在這一點上,路由器已經建立了一個路由器狀態並實例化了組件。接下來,咱們須要可以從這個路由器狀態導航到另外一個路由器狀態。有兩種方法能夠實現這一點:
this.router.navigateByUrl('/home/single/personDetail/23');
<button nz-button routerLink="/home/single/personDetail/23"></button>
複製代碼
這樣就完成了路由的整個處理過程。 成功匹配URL的結果是,一些組件集將被路由到,並經過使用router outlet
指令在屏幕上呈現。但此操做還有一個有用的反作用——建立RouterState和RouterStateSnapshot對象。
路由完成後,咱們可能但願訪問有關URL和路由到的組件集(稱爲當前路由器狀態)的信息。咱們介紹一下RouterState
和RouterStateSnapshot
屬性,它容許咱們訪問有關當前路由到的URL和組件的信息。 在重定向以後,就會獲取到將要跳轉頁面的路由器狀態RouterStateSnapshot
。 **routestastesnapshot是一種不可變的數據結構,表示路由器在特定時刻的狀態。**在組件增長、刪除或者參數變化的時候就會建立新的snapshot。 router state與RouteStateSnapshot相似,只是它表示路由器隨時間變化的狀態。
interface RouterStateSnapshot {
root: ActivatedRouteSnapshot;
}
interface ActivatedRouteSnapshot {
url: UrlSegment[];
params: {[name:string]:string};
data: {[name:string]:any};
queryParams: {[name:string]:string};
fragment: string;
root: ActivatedRouteSnapshot;
parent: ActivatedRouteSnapshot;
firstchild: ActivatedRouteSnapshot;
children: ActivatedRouteSnapshot[];
}
複製代碼
從定義能夠看出RouterStateSnapshot
是一個已經激活的路由樹,此樹中的每一個節點都知道「已使用」的URL段、提取的參數和已解析的數據。 每一個節點頭能夠經過parent和children屬性訪問它的父節點和子節點。 當咱們導航到/home/single/personDetail/23
時,路由器將查看URL並構造如下RouterStateSnapshot:
/home/single/personDetail/24
,那麼它的snapshot會變爲
爲了不沒必要要的DOM修改,當相應路由的參數改變時,路由器將複用這些組件。因此在這個例子中id由23變成了24,組件複用意味着咱們不能將ActivatedRouteSnapshot插入到PersonDetailCom中,那麼這時候組件的id仍是23,數據就存在問題了。 snapshot路由狀態是保存某一時刻的路由數據,這也是它被稱爲snapshot的緣由。可是組件會一直複用,而參數是會不斷變化的,這時候
RouterStateSnapshot
就不能知足咱們的需求了。 這就須要另外一個數據結構了:
RouterState
。
interface RouterState {
snapshot: RouterStateSnapshot; //returns current snapshot
root: ActivatedRoute;
}
interface ActivatedRoute {
snapshot: ActivatedRouteSnapshot; //returns current snapshot
url: Observable<UrlSegment[]>;
params: Observable<{[name:string]:string}>;
data: Observable<{[name:string]:any}>;
queryParams: Observable<{[name:string]:string}>;
fragment: Observable<string>;
root: ActivatedRout;
parent: ActivatedRout;
firstchild: ActivatedRout;
children: ActivatedRout[];
}
複製代碼
它的結構和RouterStateSnapshot
類似,只不過它暴露出來的值都是可觀察的,這對於處理獲取隨時間變化的值很是有用。 路由器實例化的任何組件均可以注入ActivatedRoute。
@Component(...)
class PersonDetailCom {
id: Observable<string>;
constructor(r: ActivatedRoute) {
this.id = r.data.map(d => d.id);
}
}
複製代碼
在id由23變爲24的時候,能夠獲取到最新的id值,經過此值做爲參數請求咱們須要的數據。
**ActivatedRoute提供對url、params、data、queryParams和fragment observates的訪問。**由於用戶訪問頁面是經過更改URL實現的,因此URL的變化是引發路由變化的緣由。 **每當URL改變時,路由器就從中派生出一組新的參數:**路由器接受匹配URL段的位置參數(例如':id')和最後一個匹配URL段的矩陣參數並將它們組合起來。此操做是純操做:必須更改URL才能更改參數。或者換句話說,相同的URL將始終致使相同的參數集。 **接下來,路由器調用路由的數據解析器,並將結果與提供的靜態數據相結合。**因爲數據解析器是任意的函數,路由器沒法保證在給定相同的URL時,您將得到相同的對象。URL包含資源的id,該id是固定的,數據解析器獲取該資源的內容,這些內容一般隨時間而變化。
@Component({...})
class ConversationCmp {
constructor(r: ActivatedRoute) {
/** * 獲取url **/
r.url.subscribe((s:UrlSegment[]) => {
console.log("url", s);
});
/** * 獲取參數 **/
r.params.subscribe((p => {
console.log("params", params);
});
}
}
複製代碼
看下關於data的處理,在路由配置中能夠添加data屬性,給對應路由的組件傳遞固定的data值。data屬性用於將固定對象傳遞到激活的路由。它在應用程序的整個生命週期內都不會更改。
{
path: 'personDetail/:id',
children: [
{ path: '', component: PersonDetailCom },
{
path: '',
component: HeaderCom,
outlet: 'header',
}
],
data: [
personStatus: 'activited'
],
resolve: {
conversations: ConversationsResolver
}
}
複製代碼
能夠經過訂閱ActivatedRoute
中的data屬性,獲取對應的值。
@Component({...})
class MessageCmp {
constructor(r: ActivatedRoute) {
/**
* 獲取路由配置中的data
**/
r.data.subscribe((d => {
console.log('data', d);
});
}
}
複製代碼
好了,今天先介紹這些,但願對你們有幫助。
參考文章:
Angular Router: Understanding Router State
The Three Pillars of the Angular Router
Angular-router
訂閱「前端記事本」,瞭解最新的前端信息。