Posted on Leave a comment

[功能介紹-14] Router基礎介紹

什麼是Routing?

Routing意指路由器,也就是由一個路由器來決定現在要顯示的頁面是什麼
在套用Routing時,會有下列的實踐流程
1. 套用轉址設定(讓伺服器不去真正網址所在的位置去讀取資料,而改由Routing來決定現在要顯示什麼畫面)
2. 由url分析要顯示的狀態是什麼
3. 由狀態去獲得真正要取得那些資訊
4. 從這些資訊組成實體
5. 套用導覽動作,由這個畫面切換至另一個畫面

在Angular裡,最佳做法是將載入和設定Routing放在一個top-level的模組內,並於AppModule內來import所有的路由資訊。
按照慣例,模組AppRoutingModuleapp-routing.module.ts會放在src/app

Angular的Routing 產生的虛擬 URL,並不是真的存在於檔案系統裡,因此需要伺服器能夠支援,否則重新整理時會出現404 not found。

設定base href

Router使用history.pushState來進行導航。靠著pushState,可以讓瀏覽器網頁路徑看起來像是更換真實的網址。因此Angular APP內的網址可能與伺服器的網址無法區分。
我們需要添加元素到index.html讓pushState路由能夠生效。當引用CSS或JS檔案時應用base href的值來設定相對URL路徑。

src/index.html檔案裡增加下面的文字:

<base href="/">

應被放置在在標籤的後面。不論是不是位於根目錄,都需要設置這個項目。

像是Plunker不會有固定的網站基準位置,這時可以用動態設定base path如下:

<script>document.write('<base href="' + document.location + '" />');</script>

使用Routing

在src/app/app.module.ts導入RouterModule及Routes

import { RouterModule, Routes } from '@angular/router';

在一個Angular的應用程式中只會有一個Routing,當瀏覽器的URL更改時,router會尋找Route裡確認要顯示的組件及畫面。
要使用Route,要將Router透過RouterModule.forRoot做配置並且將添加到AppModule的imports陣列
以下為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: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    //在這邊用RouterModule.forRoot來配置Router
    RouterModule.forRoot(
      appRoutes, // 設置要如何導航的資料
      { enableTracing: true } // <-- 為DEBUG而設置的
    )
    // other imports here
  &#93;,
  ...
})
export class AppModule { }&#91;/code&#93;

<h3>RouterModule.forRoot</h3>
在之前<a href="https://angular.io/guide/ngmodule#approutingmodule" rel="noopener noreferrer" target="_blank">ngModule</a>的部份,有講到如果我們希望有一個子元件能夠如同核心元件一般,比所有其他子元件都更優先被載入,或者是希望能夠在被所有元件初始化之前,優先設定好裡面的值可以用下面的方式來將元件使用forRoot方法傳回ModuleWithProviders。
[code lang="js"]static forRoot(config: UserServiceConfig): ModuleWithProviders {
  return {
    ngModule: CoreModule,
    providers: [
      {provide: UserServiceConfig, useValue: config }
    ]
  };
}

Routing就是使用這個方法,因此可以傳參數進去並且比所有其他的元件更早載入以決定現在所要顯示的頁面。

放置Router

Router會根據網址決定要顯示那個元件的VIEW,所以需要設定一個區塊來顯示要被插入的VIEW,我們可以插入下面這段文字

<router-outlet></router-outlet>
<!-- Routed views go here -->

設定Router規則

如果要設定一個連至我們設定的位置的超連結,可參考下面的寫法

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

然後就可以有如下圖的頁面功能出現:

更多Router的設定細節

更多的Router設定方式可看官方說明:Routes

下面為幾個範例:

設定子連結

[{
  path: 'team/:id',
 component: Team,
  children: [{
    path: 'user/:name',
    component: User
  }]
}]

上面的設定當我們的URL為/team/11/user/bob時,Router會顯示Team這個元件並在裡面生成一個User元件

Multiple Outlets

[{
  path: 'team/:id',
  component: Team
}, {
  path: 'chat/:user',
  component: Chat
  outlet: 'aux'
}]

上面的設定當我們URL為/team/11(aux:chat/jim)時,會先建立Team元件然後再建立Chat元件,接著將Chat元件放置至Team元件內名為aux的outlet。

百搭符號

[{
  path: '**',
  component: Sink
}]

上面的設定無論我們在那邊,都會創建Sink元件

重新轉向

[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: 'legacy/user/:name',
    redirectTo: 'user/:name'
  }, {
    path: 'user/:name',
    component: User
  }]
}]

上面的設定當我們的URL為/team/11/legacy/user/jim時,會自動導轉到/team/11/user/jim,然後產生Team元件並在裡面生成User元件。

於path設定空字串

[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: '',
    component: AllUsers
  }, {
    path: 'user/:name',
    component: User
  }]
}]

上面的設定當我們導航至/team/11,則會產生AllUsers元件實體。
空路徑也可以設定子項目

[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: '',
    component: WrapperCmp,
    children: [{
      path: 'user/:name',
      component: User
    }]
  }]
}]

當URL為/team/11/user/jim時,將會產生一個WrapperCmp實體與一個User在裡面。

設定pathMatch

[{
  path: '',
  pathMatch: 'prefix', //=>這是default值
  redirectTo: 'main'
}, {
  path: 'main',
  component: Main
}]

上面這種寫法,即使我們導航至/main,因為其前綴字符合path: ''的設定,因此Router還是會執行轉址至main的動作。
因此需要改成下面這樣

[{
  path: '',
  pathMatch: 'full',
  redirectTo: 'main'
}, {
  path: 'main',
  component: Main
}]

延遲加載

[{
  path: 'team/:id',
  component: Team,
  loadChildren: 'team'
}]

Router將使用註冊的NgModuleFactoryLoader來獲取與’team’關聯的NgModule。

RouterLink

routerLink是用來設定這個連結要連到的router位置,要填的值會需與appRoutes裡設定的path一致。
這邊有routerLink的詳細說明:https://angular.io/api/router/RouterLink

例如我們傳入queryParams與fragment的一個範例如下:

<a &#91;routerLink&#93;="&#91;'/user/bob'&#93;" &#91;queryParams&#93;="{debug: true}" fragment="education">
  link to user component
</a>

會產生網址為/user/bob#education?debug=true的連結

如果我們想要保留所有額外傳來的GET參數,可以用下面的方式來表達:
PS: 在4.0之前是使用queryParamsHandling,而在angular4.0之後停止支援這個參數,而需要改使用上面的方法

<a &#91;routerLink&#93;="&#91;'/user/bob'&#93;" preserveQueryParams preserveFragment>
  link to user component
</a>

另外我們若有設定queryParams,可以決定若是使用者原本從網址列就有傳自己的GET參數時要如何處理,有下面幾種選項

  • 'merge': merge the queryParams into the current queryParams
  • 'preserve': preserve the current queryParams
  • default/'': use the queryParams only

下面為一個使用範例

<a &#91;routerLink&#93;="&#91;'/user/bob'&#93;" &#91;queryParams&#93;="{debug: true}" queryParamsHandling="merge">
  link to user component
</a>

RouterLinkActive

routerLinkActive則是用來設定若現在的網址與所設定的連結一致時,要加上去的Class名稱
這邊有routerLinkActive的詳細說明:https://angular.io/api/router/RouterLink

我們可以傳入參數exact: true來設定是不是需要網址完全符合才顯示該class

<a routerLink="/user/bob" routerLinkActive="active-link" &#91;routerLinkActiveOptions&#93;="{exact:
true}">Bob</a>

或者如果我們希望連結符合時,能夠展開下面的子頁面時,可以用將routerLinkActive存入模版變數來使用,下面是一個範例

<a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive">
  Bob {{ rla.isActive ? '(already open)' : ''}}
</a>

取得Router目前狀態

在每個Router完成它的生命週期之後,Router將產生當前導航頁面的ActivatedRoute及RouterState。
下面為一個使用範例:

@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const state: RouterState = router.routerState;
    const root: ActivatedRoute = state.root;
    const child = root.firstChild;
    const id: Observable<string> = child.params.map(p => p.id);
    //...
  }
}

ActivatedRoute有許多有用的資訊如下:

屬性 描述
url 在router裡所設定的path的值(為一個Observable物件)。
data 在router裡所設定的value的值,亦會包含從resolve guard來的值。
paramMap 包含這個Route所設定的必需和可選參數的列表。
取代舊版angular的params
queryParamMap 所有Route可獲得的query parameter
取代舊版angular的queryParams
fragment 所有Route可獲得的HTML錨點
outlet 被使用來顯示這個Router的名稱(RouterOutlet
routeConfig 用於包含原始路徑的路由的路由配置。
parent 所取得的資料為ActivatedRoute。可獲得目前頁面在Router設定的parent。
firstChild 所取得的資料為ActivatedRoute。可獲得目前頁面下面第一個子連結。
children 包含目前路徑下的所有子連結

Router events

我們可以藉由Router物件來監聽所有此Router相關的事件,一個簡單使用範例如下:

    constructor(private router: Router) {

        router.events.subscribe( (event: Event) => {

            if (event instanceof NavigationStart) {
                // Show loading indicator
            }

            if (event instanceof NavigationEnd) {
                // Hide loading indicator
            }
        });

    }

下面為所有可監聽的狀態列表及說明:

Router Event Description
NavigationStart 當導航開始時會觸發
RoutesRecognized 當Router正在解析URL及Routes時觸發
RouteConfigLoadStart 在Lazy loaded router配置之前觸發的事件。
RouteConfigLoadEnd Lazy loaded router載入後觸發的事件
NavigationEnd 導航成功完成後觸發
NavigationCancel 導航取消時觸發
NavigationError 由於意外錯誤導致導航失敗時觸發
Leave a Reply