在講完去Module的Angular component開發與資料處理的Action=>Effect=>Reducer=>Selector之後,讓我們來完成系統開發所必須知道的最後一個知識-Router
就像先前講系統設計的時候所說的DDD開發準則一樣,我們前端開發的過程中勢必會比後端開發更加接近使用者的功能開發,以前端的邏輯就是我們要設計使用者的UX並且將其實作起來
對於使用者的體驗設計在畫面設計之外,操作情境的流暢度與服務切分也是我們必須關注的點。
路由設計就是第一個保證我們的服務切分能夠順利貼切於使用者體驗的第一步
在設計上當使用者進到我們的服務時,導入的首頁的樣板就已經開始了我們的UX實作
我會先根據Designer所提供的設計稿,將幾個共同的 Template
佈版抽離出來來進行我們自己的佈版設計
有些時候有些Template佈版的設計會需要顯示 Search box
、 Header
、 Slider Meanu(側邊Meanu)
,但有些Template會希望僅顯示功能頁面就好。前者的案例舉凡各種服務的首頁,當然最著名的就是Google , 後者則是Google Map的螢幕範圍最大化 - 不會變成佔據螢幕的真正的**"全螢幕"**,同樣的效果也可以應用在商品明細或是檢視圖表放大的狀況中
接下來我們將進行Template佈版的設計與實作,先在 shared
建立一個folder templates
,然後在裡面建立一個Component IndexLayoutComponent
,然後將我們的 <app-header />
放進 <app-index-layout / >
<app-header></app-header>
那接下來要怎麼讓我們可以將這個 Template 共同實作到某些路由( a.k.a scenario使用者情境) 之下呢?
這邊就要先實作App的router,同樣也是應用在原先在AppModule,現在是在app.config.ts的 appConfig
之中
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { provideHttpClient } from '@angular/common/http';
import { userReducerKey } from './core/store/reducers/user.reducers';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),//<--這裡
provideStore({
[layoutReducerKey]: reducer[layoutReducerKey],
[userReducerKey]: reducer[userReducerKey],
}),
provideHttpClient(),
provideEffects(effects),
],
};
雖然ng new AngularProduct
的時候,app.router.ts
(以前應該會是router.module.ts),會自動幫我們時坐在app
這個folder之下,但根據先前所提出的系統架構Core-Shared-Feature的設計中,僅載入一次的這個router我會將其重新調整位置到core > route > app.route.ts
之中
app/
| |-core/
| | |-routers/
| | | |-app.route.ts
在設計上所有的Templates都應該僅載入一次,我們就在這個Router之中進行實作
其實實作也很簡單,我只要在Routes之中的 path 之中將想要渲染的template放入component
之中就可以了
app.route.ts
import { Routes } from '@angular/router';
import { IndexLayoutComponent } from '../../shared/templates/index-layout/index-layout.component';
export const routes: Routes = [
{
path: 'eShopping',
component: IndexLayoutComponent,//<--這裡
loadChildren: () => import('../../feature/e-shopping/e-shopping.routes'),
},
{
path: '**',
redirectTo: '/eShopping/Home',
pathMatch: 'full',
},
];
另外我們在 IndexLayoutComponent
之中載入
index-layout.html
<app-header></app-header>
<router-outlet></router-outlet>
另外最重要的是,記得在Component中載入RouteOutlet
,不然完全無法實作起來
index-layout.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';//<--一定要載入
import { HeaderModule } from '../../components/header/header.module';
@Component({
standalone: true,
imports: [CommonModule, RouterOutlet, HeaderModule],
templateUrl: './index-layout.component.html',
styleUrls: ['./index-layout.component.scss'],
})
export class IndexLayoutComponent {}
假如我沒有打算到下一層route的話,就可以不用載入RouterOutlet
同時,假如有下一層的話,另外的route.ts我都會follow Feature來進行route的設定,方便我們整握UX的脈絡
架構大致如下
feature/
| |-e-shopping/
| | |-home/
...
| | |-e-shopping.routes.ts
而最終端沒有子層級的Feature就不用另外建立routes.ts,同理,也不需要用loadChildren
來進行子路由的lazyLoad載入,至於終端的routes.ts的示例如下
e-shopping.routes.ts
import { Route } from '@angular/router';
import { ShoppingHomeComponent } from './shopping-home/shopping-home.component';
export default [
{ path: 'Home', component: ShoppingHomeComponent },
{ path: '**', redirectTo: '/eShopping/Home' },
] as Route[];
現在應該會在每次的npm start
時候,因為載入不到有意義的路由,都被redirectTo
導回至我們的首頁了。
除此之外Angular也能做到動態路由 /:id(or其他自行宣告參數)
動態路由其實也很簡單 我們可以透過宣告路由宣告,就可以實現route與url的變化
import { Route } from '@angular/router';
import { ShoppingHomeComponent } from './shopping-home/shopping-home.component';
import { ShoppingDetailComponent } from './shopping-detail/shopping-detail.component';
export default [
{ path: 'Home', component: ShoppingHomeComponent },
{ path: 'ShoppingDetail/:id', component: ShoppingDetailComponent },//<--這裡
{ path: '**', redirectTo: '/eShopping/Home' },
] as Route[];
而我要進行取用的話則是在**:id**宣告的componentShoppingDetailComponent
進行監聽
範例如下
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
@Component({
standalone: true,
imports: [CommonModule],
templateUrl: './shopping-detail.component.html',
styleUrls: ['./shopping-detail.component.scss'],
})
export class ShoppingDetailComponent implements OnInit {
private activeRouter = inject(ActivatedRoute);
ngOnInit(): void {
this.getDetailID();
}
getDetailID(): void {
const id = this.activeRouter.snapshot.paramMap.get('id');
console.log(id);
}
}
這樣子我們就能取得到我們的動態路由id,後面要進行Service的實作、發起SAction或是API都可以
明天我們將會講講要怎麼在url中塞入params以及路由守衛