title: Angular 路由器
date: 2017-08-30
categories: Training
keywords:


路由 (Routing)

Angular 元件 最後面我們練習了直接透過 tag 來嵌入 Component,但是這只是在頁面一開始呈現的效果,而正常情況下還需要依照使用者的操作適當的改變內容,例如切換不同功能時需要整個頁面替換掉,做查詢時可能要在介面上的某個區塊將結果顯示出來。
Angular 提供了一個路由機制,讓我們可以很方便的抽部分換區塊,說是部分換區塊是因為正常情況下,Angular 的根元件(起始模組內所指定的起始元件)在一開始載入後便無法被抽換掉,我們只能替換根元件內的內容。

建立 Angular 專案 我們在建立專案時透過指令 ng new first-app --routing --style scss 來建立專案,其中帶了一個參數 --routing,此參數讓 CLI 在建立專案同時幫我們新增一個具有路由功能的模組― AppRoutingModule,並將該模組註冊到 AppModule 內。
開啟 scr\app\app-routing.module.ts 可以發現裡面宣告一個 routes 的陣列變數讓我們可以加入多個路由規則,最後 RouterModule.forRoot(routes) 則是將此路由變數宣告為根路由。

{% codeblock app-routing.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;

const routes: Routes = [
{
path: ‘’,
children: []
}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

{% endcodeblock %}

之前將 Component 註冊到模組時是加在 NgModuledeclarations 屬性內,但是由程式碼可知 NgModule 要註冊到另一個 NgModule 時,則必須加到 imports 屬性內。

{% codeblock app.module.ts lang:ts %}
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;

import { AppRoutingModule } from ‘./app-routing.module’;
import { AppComponent } from ‘./app.component’;
import { Page1Component } from ‘./page1/page1.component’;
import { Page2Component } from ‘./page2.component’;
import { Page3Component } from ‘./page3.component’;

@NgModule({
declarations: [
AppComponent,
Page1Component,
Page2Component,
Page3Component
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

{% endcodeblock %}

路由插座 (router-outlet)

大家之前若有注意到 CLI 所建立的專案內的 app.component.html,網頁內的最後一行是一個奇怪的 tag- <router-outlet></router-outlet>,如果大家跟我一樣健忘,但是電腦內有剛好有安裝 git,那麼就可以直接利用 VS Code 的原始檔控制來回溯內容。

git 工具可以至 https://git-scm.com/ 網站下載安裝。

點選原始檔控制然後選擇 app.component.html 及可看到目前內容與先前的內容。
img
或者可以參考下面的原始程式碼第21行。(src\app\app.component.html)

{% codeblock app.component.html lang:html %}

Welcome to { {title} }!

Here are some links to help you start:

{% endcodeblock %}

我們可以把 router-outle 看成 Component 的 tag,因為 Angular 的路由機制運作結果會如同 Component 的 tag,會以這個 tag 當作標記去插入指定的 Component。

練習編寫路由規則

移除 app.component.html 所有內容,只添加一個 <router-outlet></router-outlet>,這樣執行起來畫面會完全空白,如果畫面上出現東西就表示路由機制運作成功。

{% codeblock app.component.html lang:html %}

{% endcodeblock %}

開啟 app-routing.module.ts ,加入3個路由規則分別對應到 Page1Component、Page2Component、Page3Component。

當導覽路徑變化時 Angular Router 會將導覽路徑拿來跟路由規則逐一比對,如果路徑與 path 條件符合就會執行該路由條件,因為目前我們設定了 component 屬性,所以符合條件時就會將 component 屬性所指定的 Component 插入目前 Component 的 router-outlet tag 內。

{% codeblock app-routing.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;
import { Page1Component } from ‘./page1/page1.component’;
import { Page2Component } from ‘./page2.component’;
import { Page3Component } from ‘./page3.component’;

const routes: Routes = [
{ path: ‘’, children: [] },
{ path: ‘p1’, component: Page1Component },
{ path: ‘p2’, component: Page2Component },
{ path: ‘p3’, component: Page3Component }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

{% endcodeblock %}

路由規則比對時,一旦比對到符合條件的規則後續就不會再做比對,所以路由規則是有順序性

透過 ng serve 啟動,並分別連結至 http://localhost:4200/p1http://localhost:4200/p2http://localhost:4200/p3,可以看到每個網址都顯示出對應的 Component 內容。
img
嘗試輸入不存在的路由規則,例如:http://localhost:4200/p4,可以看到 Angular 好像自動幫我們導回到**http://localhost:4200/**,可是開啟瀏覽器開發者工具就會發現系統發出錯誤的訊息。
img

萬用路由 (**)

Angular Router 提供一個萬用路由規則,當 path 屬性設定為 '**' 時表示條件為任意值,路由比對到這一個規則時一定會符合,所以設置在此規則後面的路由規則基本上是無效的,因為任何導覽路徑到了萬用路由就會被攔截調,所以正常會將萬用路由放置在路由規則的最後面

練習加入萬用路由

嘗試加入一個萬用路由,並改用 redirectTo 屬性, 將其設定為 ‘p1’。

{% codeblock app-routing.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;
import { Page1Component } from ‘./page1/page1.component’;
import { Page2Component } from ‘./page2.component’;
import { Page3Component } from ‘./page3.component’;

const routes: Routes = [
{ path: ‘’, children: [] },
{ path: ‘p1’, component: Page1Component },
{ path: ‘p2’, component: Page2Component },
{ path: ‘p3’, component: Page3Component },
{ path: ‘**’, redirectTo: ‘’}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

{% endcodeblock %}

開啟瀏覽器在 http://localhost:4200/ 網址後面輸入任何非 p1p2p3,例如:http://localhost:4200/p4http://localhost:4200/abc,會發現不論輸入任何值都會被重新導引到首頁(http://localhost:4200/),從瀏覽器開發者工具並不會看到任何錯誤訊息。
一般而言萬用路由不應該被執行到,因為這表示目前導覽路徑是一個非預期的路徑,所以萬用路由會比較像是為了避免系統出錯的預防機制,比較常見的情境是

練習製作 HTTP 404 頁面

透過指令 ng g c page404 建立一個,並修改 src\app\page404\page404.component.html 內容如下:

{% codeblock page404.component.html lang:html %}

404 Page Not Found

{% endcodeblock %}
修改 app-routing.module.ts 將萬用路由指向 Page404Component
{% codeblock app-routing.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;
import { Page1Component } from ‘./page1/page1.component’;
import { Page2Component } from ‘./page2.component’;
import { Page3Component } from ‘./page3.component’;
import { Page404Component } from ‘./page404/page404.component’;

const routes: Routes = [
{ path: ‘’, children: [] },
{ path: ‘p1’, component: Page1Component },
{ path: ‘p2’, component: Page2Component },
{ path: ‘p3’, component: Page3Component },
// { path: ‘‘, redirectTo: ‘’}
{ path: ‘
‘, component: Page404Component }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

{% endcodeblock %}

重新瀏覽任意不存在的路徑,如 http://localhost:4200/abc ,發現會一律顯示 Page404Component 的內容,但是導覽路徑不會改變。
img
如果希望導覽路徑跟著改變可以調整路由規則如下:

{% codeblock app-routing.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;
import { Page1Component } from ‘./page1/page1.component’;
import { Page2Component } from ‘./page2.component’;
import { Page3Component } from ‘./page3.component’;
import { Page404Component } from ‘./page404/page404.component’;

const routes: Routes = [
{ path: ‘’, children: [] },
{ path: ‘p1’, component: Page1Component },
{ path: ‘p2’, component: Page2Component },
{ path: ‘p3’, component: Page3Component },
{ path: ‘404’, component: Page404Component },
{ path: ‘‘, redirectTo: ‘404’ }
// { path: ‘
‘, redirectTo: ‘’}
// { path: ‘**’, component: Page404Component }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

{% endcodeblock %}

任何錯誤路徑都會被重新導覽到http://localhost:4200/404
img

我們可以發現路由規則其實都是在比較相對路徑,我們在 path 並不會輸入完整路徑,Angular 又要如何知道應用程式的根路徑?
開啟 index.html 檔會發現 head 區間內包含了一個 <base href="/">,Angular Router 便是透過 base 這個 tag 來依序組合導覽路徑。

{% blockquote W3Schools https://www.w3schools.com/tags/tag_base.asp HTML 標籤 %}

標籤指定文檔中所有相對URL的基本URL /目標。 文檔中最多可以有一個元素,它必須在元素內。 {% endblockquote %}

{% img /images/download.png 36 %}first-app_2017-08-30.zip