SuperAPI AI Directory

angular-primeng

Frontend & websites

View repository

Updated 10-16 01:28

About this skill

Angular 21 + PrimeNG Development Guidelines

Applicable Scenarios

  • MES Manufacturing Execution System
  • ERP Enterprise Resource Planning
  • Backend Management System
  • Data-Intensive Applications

Core Principles

1. Project Structure

src/app/
├── core/                    # 核心模組(單例服務)
│   ├── auth/               # 認證相關
│   ├── guards/             # 路由守衛
│   ├── interceptors/       # HTTP 攔截器
│   ├── models/             # 資料模型
│   └── services/           # 共用服務
├── shared/                  # 共用模組
│   ├── components/         # 可復用元件
│   ├── directives/         # 自定義指令
│   ├── pipes/              # 資料管線
│   └── utils/              # 工具函數
├── features/               # 功能模組
│   ├── dashboard/
│   ├── production/
│   └── settings/
└── layout/                 # 版面配置

2. PrimeNG Configuration (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 Component Reference Table

Purpose Component Example
Form/Detail Sidebar <p-drawer> Edit work order, add customer
Confirmation Dialog <p-dialog> Delete confirmation, warning message
Data Table <p-table> Work order list, report
Button <p-button> All button operations
Text Input <input pInputText> Used with ngModel
Dropdown <p-select> Status selection, category
Date Picker <p-datepicker> Date range filtering
Tag/Badge <p-tag> Status indication
Toast Notification <p-toast> Operation feedback
Loading Mask <p-blockui> Asynchronous operation

4. Signals State Management (Angular 21)

// 推薦:使用 Signals
import { signal, computed, effect } from '@angular/core';

@Component({...})
export class WorkOrderListComponent {
  // 狀態
  workOrders = signal<WorkOrder[]>([]);
  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. Service Layer Design

@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 Response Format

// 統一回應格式
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. Form Handling

// 推薦:Reactive Forms + PrimeNG
@Component({
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div class="field">
        <label for="orderNumber">工單編號</label>
        <input pInputText id="orderNumber" formControlName="orderNumber" />
        <small *ngIf="form.get('orderNumber')?.errors?.['required']" class="p-error">
          必填欄位
        </small>
      </div>

      <div class="field">
        <label for="customer">客戶</label>
        <p-select
          id="customer"
          formControlName="customerId"
          [options]="customers()"
          optionLabel="name"
          optionValue="id"
          placeholder="選擇客戶"
        />
      </div>

      <div class="field">
        <label for="dueDate">交期</label>
        <p-datepicker
          id="dueDate"
          formControlName="dueDate"
          dateFormat="yy-mm-dd"
        />
      </div>

      <p-button type="submit" label="儲存" [loading]="isSubmitting()" />
    </form>
  `
})
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. Table Best Practices

@Component({
  template: `
    <p-table
      [value]="workOrders()"
      [paginator]="true"
      [rows]="20"
      [rowsPerPageOptions]="[10, 20, 50]"
      [loading]="isLoading()"
      [globalFilterFields]="['orderNumber', 'customerName']"
      styleClass="p-datatable-sm"
    >
      <ng-template pTemplate="header">
        <tr>
          <th pSortableColumn="orderNumber">
            工單編號 <p-sortIcon field="orderNumber" />
          </th>
          <th pSortableColumn="status">狀態</th>
          <th>操作</th>
        </tr>
      </ng-template>

      <ng-template pTemplate="body" let-order>
        <tr>
          <td>{{ order.orderNumber }}</td>
          <td>
            <p-tag [value]="order.status" [severity]="getStatusSeverity(order.status)" />
          </td>
          <td>
            <p-button icon="pi pi-pencil" [text]="true" (click)="edit(order)" />
            <p-button icon="pi pi-trash" [text]="true" severity="danger" (click)="confirmDelete(order)" />
          </td>
        </tr>
      </ng-template>

      <ng-template pTemplate="emptymessage">
        <tr>
          <td colspan="3" class="text-center">無資料</td>
        </tr>
      </ng-template>
    </p-table>
  `
})
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 Sidebar Pattern

@Component({
  template: `
    <p-drawer
      [(visible)]="drawerVisible"
      [header]="isEditMode() ? '編輯工單' : '新增工單'"
      position="right"
      [style]="{ width: '500px' }"
      (onHide)="onClose()"
    >
      <app-work-order-form
        [workOrder]="selectedOrder()"
        (save)="onSave($event)"
        (cancel)="drawerVisible = false"
      />
    </p-drawer>
  `
})
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. Error Handling

// 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);
      })
    );
  }
}

Prohibitions

  1. Prohibit Fallback Mock Data - API failures should display errors; fake data must not be used
  2. Prohibit Emoji - Do not use emoji in code, comments, or UI
  3. Prohibit Simplified Chinese Characters - Use Traditional Chinese (Taiwan terminology) throughout
  4. Prohibit Single Files Over 500 Lines - Must be split if exceeded

Performance Optimization

  1. OnPush Change Detection - Use with Signals
  2. trackBy - Table lists must use it
  3. Lazy Loading - Feature modules lazy loading
  4. Virtual Scrolling - Use for large amounts of data <p-scroller>

Reference Resources