路由在網頁是非常常見的功能,在 Angular 也有屬於自己的路由系統 (Angular 真的好多功能都是內建!)。Angular 路由系統是藉由 component 的替換,來決定當下畫面要顯示什麼。Angular 是屬於 SPA (Single Page Application),所以換頁的動作會在前端完成,在這裡並不會跟伺服器端請求。
接續上次的程式碼,我們需要另外的 component 讓我們可以換頁,如下圖所示,目標是希望點擊管理菜單後(藍色框框),可以轉跳到項目清單的頁面,但這裡先不真的實作項目清單的細節。
所以我的在之前建立的 product module 的資料夾下,輸入以下指令,建立需要的資料夾與 component (這些資料夾的用處可參考之前的文章)
cd ./src/app/product
mkdir containers components services models
touch index
ng g c ./containers/product-list
由於我們預計項目清單的功能可能會越來越複雜,所以在這裡設計這個模組會是懶載入。首先開啟 product-routing.module.ts 檔案,設定路由的路徑 (第 5 行),以及對應的 component (第 6 行),與設定提供給當前路由的 data 物件的 Observable (第 7 行,用來顯示在 header 的標題,後面會再提到)
// product-routing.module.ts
// ...省略
const routes: Routes = [
{
path: '',
component: ProductListComponent,
data: { moduleName: '項目清單' },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ProductRoutingModule {}
在 merchant-routing.module.ts 裡也是一樣的設置方式
// merchant-routing.module.ts
// ...省略
const routes: Routes = [
{
path: '',
component: MerchantListComponent,
data: { moduleName: '商家菜單' },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class MerchantRoutingModule {}
而在 app-routing.module.ts 就比較不一樣了。在 AppComponent 我們會先載入 LayoutComponent (第 6 行),在 LayoutComponent 裡面去置換 merchant module 與 product module 裡的 component,這裡的語法是懶載入的寫法,兩個 module 的路由在剛剛已經定義好了(第 9~12 行與第 16~18 行),此外,第 20、23 行是萬用字元路徑,就是當沒有匹配路徑時,會重新導向到指定路徑。
因為懶載入的關係,先前匯入 AppModule 的 MerchantModule 可以拿掉了
// app-routing.module.ts
// ...省略
const routes: Routes = [
{
path: '',
component: LayoutComponent,
children: [
{
path: 'merchant',
loadChildren: () =>
import('./merchant/merchant.module').then(
(mod) => mod.MerchantModule
),
},
{
path: 'product/:merchantId',
loadChildren: () =>
import('./product/product.module').then((mod) => mod.ProductModule),
},
{ path: '**', redirectTo: 'merchant' },
],
},
{ path: '**', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
router-outlet
當路徑都設好後,接下來要改 html 的部分,就是利用 router-outlet
tag 在 html 內排版。我們先修改 app.component.html,這裡到時候會帶入剛剛設定的 LayoutComponent
<app-nav class="mat-elevation-z6"></app-nav>
<router-outlet></router-outlet>
所以,我們在往 layout.component.html 設定,在這裡也是利用 router-outlet
加到在 html 內,另外還用 Template reference variables 存入 outlet 物件(第 6 行),與用 activate 事件綁定,當路由在轉換時會呼叫 setModuleName (第 7 行)
<div class="sidenav-inner-content">
<app-header [headText]="moduleName"></app-header>
<main class="sidenav-body-content">
<app-sidenav></app-sidenav>
<router-outlet
#routerOutlet="outlet"
(activate)="setModuleName(routerOutlet)"
></router-outlet>
</main>
<app-footer></app-footer>
</div>
setModuleName 方法,會傳來把 outlet 物件裡的 data (剛剛在上面設定 product 與 merchant 路由路徑時帶的 data 物件)
,指定給 moduleName 屬性,由於 HeaderComponent 有屬性綁定 moduleName,所以當畫面轉換時, Header 會依 data 的值一起改變。
// layout.component.ts
// ...省略
export class LayoutComponent implements OnInit {
moduleName;
constructor() {}
ngOnInit(): void {}
setModuleName(outlet: RouterOutlet): void {
const moduleName = outlet.activatedRouteData.moduleName;
this.moduleName = moduleName;
}
}
這裡的 LayoutModule 之前沒匯入 RouterModule,因為會用到
router-outlet
所以需要匯入。
routerLink
,觸發轉跳頁面接下來要修改 merchant-item.component.html,在管理菜單的按鈕加上routerLink
,並提供相對於現在位置要到達到路徑(第 5 行)。5這樣就大功告成了。
<!-- ...省略 -->
<mat-card-actions>
<a
mat-stroked-button
[routerLink]="['..', 'product', merchant.id]"
(click)="$event.stopPropagation()"
>
管理菜單
</a>
<button
mat-icon-button
class="card-delete-btn"
(click)="deleteMerchant(merchant.id)"
>
<mat-icon>delete</mat-icon>
</button>
</mat-card-actions>
<!-- ...省略 -->
今天說完了 router 的概念了,也是非常簡單的說明而已,事實上還有很多很深的實作,不過目前的專案這樣算是夠用了。完整的範例程式碼。下一篇會講用 Angular 的 http client 來發送 request。