Angular路由管理過程淺談

最近在使用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與要呈現的適當組件集相關聯,則沒法在應用程序中導航。因此咱們須要瞭解整個URL的訪問過程 好比咱們要訪問: home/single/personDetail/23 bash

一個angular路由對url的處理過程:

  • 處理重定向
  • 識別路由狀態
  • 進行路由守衛和處理數據
  • 激活全部相關的組件
  • 管理導航
處理重定向

重定向:重定向是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],
  }
複製代碼

能夠看到這部分代碼,有canLoadcanActivateChildcanActivate幾個節點,表示在路由執行的這幾個節點都須要執行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添加了兩個組件PersonDetailComHeaderCom,而且HeaderCom指定了outletheader。 這樣,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上的參數,經過參數獲取數據。

導航

在這一點上,路由器已經建立了一個路由器狀態並實例化了組件。接下來,咱們須要可以從這個路由器狀態導航到另外一個路由器狀態。有兩種方法能夠實現這一點:

  • 強制調用router.navigate
  • 聲明性地使用RouterLink指令
this.router.navigateByUrl('/home/single/personDetail/23');

<button nz-button routerLink="/home/single/personDetail/23"></button>
複製代碼

這樣就完成了路由的整個處理過程。 成功匹配URL的結果是,一些組件集將被路由到,並經過使用router outlet指令在屏幕上呈現。但此操做還有一個有用的反作用——建立RouterState和RouterStateSnapshot對象。

RouterState和RouterStateSnapshot

路由完成後,咱們可能但願訪問有關URL和路由到的組件集(稱爲當前路由器狀態)的信息。咱們介紹一下RouterStateRouterStateSnapshot屬性,它容許咱們訪問有關當前路由到的URL和組件的信息。 在重定向以後,就會獲取到將要跳轉頁面的路由器狀態RouterStateSnapshot。 **routestastesnapshot是一種不可變的數據結構,表示路由器在特定時刻的狀態。**在組件增長、刪除或者參數變化的時候就會建立新的snapshot。 router state與RouteStateSnapshot相似,只是它表示路由器隨時間變化的狀態。

RouterStateSnapshot

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

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

**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

訂閱「前端記事本」,瞭解最新的前端信息。

相關文章
相關標籤/搜索