title: Angular 路由器
date: 2017-08-30
categories: Training
keywords:
在 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 註冊到模組時是加在 NgModule 的 declarations 屬性內,但是由程式碼可知 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 %}
大家之前若有注意到 CLI 所建立的專案內的 app.component.html
,網頁內的最後一行是一個奇怪的 tag- <router-outlet></router-outlet>
,如果大家跟我一樣健忘,但是電腦內有剛好有安裝 git,那麼就可以直接利用 VS Code 的原始檔控制來回溯內容。
git 工具可以至 https://git-scm.com/ 網站下載安裝。
點選原始檔控制然後選擇 app.component.html
及可看到目前內容與先前的內容。
或者可以參考下面的原始程式碼第21行。(src\app\app.component.html
)
{% codeblock app.component.html lang:html %}
{% 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。
path
:設定導覽的相對路徑component
:設定要載入的 Component當導覽路徑變化時 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/p1、http://localhost:4200/p2、http://localhost:4200/p3,可以看到每個網址都顯示出對應的 Component 內容。
嘗試輸入不存在的路由規則,例如:http://localhost:4200/p4,可以看到 Angular 好像自動幫我們導回到**http://localhost:4200/**,可是開啟瀏覽器開發者工具就會發現系統發出錯誤的訊息。
Angular Router 提供一個萬用路由規則,當 path
屬性設定為 '**'
時表示條件為任意值,路由比對到這一個規則時一定會符合,所以設置在此規則後面的路由規則基本上是無效的,因為任何導覽路徑到了萬用路由就會被攔截調,所以正常會將萬用路由放置在路由規則的最後面。
嘗試加入一個萬用路由,並改用 redirectTo
屬性, 將其設定為 ‘p1’。
redirectTo
:表示當路由規則符合時會重新導覽到 redirectTo 所指定的路由路徑。{% 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/
網址後面輸入任何非 p1
、p2
、p3
,例如:http://localhost:4200/p4
、http://localhost:4200/abc
,會發現不論輸入任何值都會被重新導引到首頁(http://localhost:4200/
),從瀏覽器開發者工具並不會看到任何錯誤訊息。
一般而言萬用路由不應該被執行到,因為這表示目前導覽路徑是一個非預期的路徑,所以萬用路由會比較像是為了避免系統出錯的預防機制,比較常見的情境是
導覽至首頁:此種情境適合在發生錯誤時使用者可以捨棄目前資訊,大部分來說這種非預期狀況是一般使用者無法排除的,因此導引至首頁可以讓使用者繼續操作,在 PWA(Progressive Web App) 網站應該是不錯的選擇。
Page Not Found:目前網站比較普遍的作法是建立一個 HTTP 404 頁面,對於比較需要立即修正的網站會比較適合,除了可以藉由 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 的內容,但是導覽路徑不會改變。
如果希望導覽路徑跟著改變可以調整路由規則如下:
{% 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
。
我們可以發現路由規則其實都是在比較相對路徑,我們在
path
並不會輸入完整路徑,Angular 又要如何知道應用程式的根路徑?
開啟 index.html 檔會發現 head 區間內包含了一個<base href="/">
,Angular Router 便是透過base
這個 tag 來依序組合導覽路徑。
{% blockquote W3Schools https://www.w3schools.com/tags/tag_base.asp HTML
{% img /images/download.png 36 %}first-app_2017-08-30.zip