Angular 21 + PrimeNG 開發規範
適用場景
MES 製造執行系統
ERP 企業資源規劃
後台管理系統
資料密集型應用
核心原則
1. 專案結構
src/app/
├── core/ # 核心模組(單例服務)
│ ├── auth/ # 認證相關
│ ├── guards/ # 路由守衛
│ ├── interceptors/ # HTTP 攔截器
│ ├── models/ # 資料模型
│ └── services/ # 共用服務
├── shared/ # 共用模組
│ ├── components/ # 可復用元件
│ ├── directives/ # 自定義指令
│ ├── pipes/ # 資料管線
│ └── utils/ # 工具函數
├── features/ # 功能模組
│ ├── dashboard/
│ ├── production/
│ └── settings/
└── layout/ # 版面配置
2. PrimeNG 設定 (app.config.ts)
import
{
ApplicationConfig
}
from
'@angular/core'
;
import
{
provideAnimationsAsync
}
from
'@angular/platform-browser/animations/async'
;
import
{
providePrimeNG
}
from
'primeng/config'
;
import
Aura
from
'@primeng/themes/aura'
;
export
const
appConfig
:
ApplicationConfig
=
{
providers
:
[
provideAnimationsAsync
(
)
,
providePrimeNG
(
{
theme
:
{
preset
:
Aura
,
options
:
{
darkModeSelector
:
'.dark-mode'
,
cssLayer
:
{
name
:
'primeng'
,
order
:
'tailwind-base, primeng, tailwind-utilities'
}
}
}
,
ripple
:
true
}
)
]
}
;
3. PrimeNG 元件對照表
用途
元件
範例
表單/詳情側邊欄
( [ ] ) ; selectedId = signal < string | null
( null ) ; isLoading = signal ( false ) ; // 計算屬性 selectedOrder = computed ( ( ) => this . workOrders ( ) . find ( o => o . id === this . selectedId ( ) ) ) ; pendingCount = computed ( ( ) => this . workOrders ( ) . filter ( o => o . status === 'pending' ) . length ) ; // 副作用 constructor ( ) { effect ( ( ) => { console . log ( '選中的工單:' , this . selectedOrder ( ) ) ; } ) ; } } 5. 服務層設計 @ Injectable ( { providedIn : 'root' } ) export class WorkOrderService { private apiUrl = environment . apiUrl + '/work-orders' ; constructor ( private http : HttpClient ) { } // 列表查詢(支援篩選) getList ( params ? : WorkOrderQueryParams ) : Observable < ApiResponse < WorkOrder [ ]
{ return this . http . get < ApiResponse < WorkOrder [ ]
( this . apiUrl , { params } ) ; } // 單筆查詢 getById ( id : string ) : Observable < ApiResponse < WorkOrder
{ return this . http . get < ApiResponse < WorkOrder
(
${ this . apiUrl } / ${ id }) ; } // 新增 create ( data : CreateWorkOrderDto ) : Observable < ApiResponse < WorkOrder{ return this . http . post < ApiResponse < WorkOrder
( this . apiUrl , data ) ; } // 更新 update ( id : string , data : UpdateWorkOrderDto ) : Observable < ApiResponse < WorkOrder
{ return this . http . put < ApiResponse < WorkOrder
(
${ this . apiUrl } / ${ id }, data ) ; } // 刪除 delete ( id : string ) : Observable < ApiResponse < void{ return this . http . delete < ApiResponse < void
(
${ this . apiUrl } / ${ id }) ; } } 6. API 回應格式 // 統一回應格式 interface ApiResponse < T{ success : boolean ; data ? : T ; error ? : { code : string ; message : string ; } ; } // 分頁回應 interface PaginatedResponse < T
extends ApiResponse < T [ ]
{ pagination : { total : number ; page : number ; limit : number ; } ; } 7. 表單處理 // 推薦:Reactive Forms + PrimeNG @ Component ( { template : `
` } ) export class WorkOrderFormComponent { form = new FormGroup ( { orderNumber : new FormControl ( '' , Validators . required ) , customerId : new FormControl ( '' , Validators . required ) , dueDate : new FormControl < Date | null
( null ) } ) ; customers = signal < Customer [ ]
( [ ] ) ; isSubmitting = signal ( false ) ; } 8. 表格最佳實踐 @ Component ( { template : ` <p-table [value]="workOrders()" [paginator]="true" [rows]="20" [rowsPerPageOptions]="[10, 20, 50]" [loading]="isLoading()" [globalFilterFields]="['orderNumber', 'customerName']" styleClass="p-datatable-sm"
` } ) export class WorkOrderTableComponent { workOrders = signal < WorkOrder [ ]
( [ ] ) ; isLoading = signal ( false ) ; getStatusSeverity ( status : string ) : 'success' | 'info' | 'warn' | 'danger' { const map : Record < string , 'success' | 'info' | 'warn' | 'danger'
= { completed : 'success' , in_progress : 'info' , pending : 'warn' , cancelled : 'danger' } ; return map [ status ] || 'info' ; } } 9. Drawer 側邊欄模式 @ Component ( { template : ` <p-drawer [(visible)]="drawerVisible" [header]="isEditMode() ? '編輯工單' : '新增工單'" position="right" [style]="{ width: '500px' }" (onHide)="onClose()"
` } ) export class WorkOrderListComponent { drawerVisible = false ; selectedOrder = signal < WorkOrder | null ( null ) ; isEditMode = computed ( ( ) => this . selectedOrder ( ) !== null ) ; openCreate ( ) { this . selectedOrder . set ( null ) ; this . drawerVisible = true ; } openEdit ( order : WorkOrder ) { this . selectedOrder . set ( order ) ; this . drawerVisible = true ; } } 10. 錯誤處理 // HTTP 攔截器 @ Injectable ( ) export class ErrorInterceptor implements HttpInterceptor { constructor ( private messageService : MessageService ) { } intercept ( req : HttpRequest < any
, next : HttpHandler ) : Observable < HttpEvent < any
{ return next . handle ( req ) . pipe ( catchError ( ( error : HttpErrorResponse ) => { let message = '發生錯誤,請稍後再試' ; if ( error . status === 401 ) { message = '請重新登入' ; } else if ( error . status === 403 ) { message = '權限不足' ; } else if ( error . status === 404 ) { message = '資料不存在' ; } else if ( error . status === 503 ) { message = '服務暫時無法使用' ; } else if ( error . error ?. error ?. message ) { message = error . error . error . message ; } this . messageService . add ( { severity : 'error' , summary : '錯誤' , detail : message } ) ; return throwError ( ( ) => error ) ; } ) ) ; } } 禁止事項 禁止 Fallback Mock 資料 - API 失敗應顯示錯誤,不得使用假資料 禁止 Emoji - 程式碼、註解、UI 都不使用 emoji 禁止簡體字 - 全程使用正體中文(台灣用語) 禁止單檔超過 500 行 - 超過需拆分 效能優化 OnPush 變更偵測 - 搭配 Signals 使用 trackBy - 表格列表必須使用 Lazy Loading - 功能模組延遲載入 虛擬捲動 - 大量資料使用
參考資源 Angular Style Guide PrimeNG Documentation Angular Signals