在上一篇,我們學會了如何處理 群組級驗證與跨欄位邏輯,解決了「欄位之間有依存關係」的問題。
而在實務專案裡,還有一個常見的需求是:
👉 使用者需要輸入多筆相同結構的資料,但是數量不一定。
舉例:
這些場景,光靠 FormGroup 不夠,需要用到 FormArray 來表示「一組陣列型的表單集合」。
FormGroup
FormArray
可以把 FormArray 想成「裝著一群 FormGroup(或 FormControl)的陣列」。
需求
程式碼
import { FormBuilder, FormArray, Validators } from '@angular/forms';
form = this.fb.group({
orderItems: this.fb.array([
this.fb.group({
productName: ['iPhone 15', Validators.required],
quantity: [2, [Validators.required, Validators.min(1)]],
price: [35000, [Validators.required, Validators.min(1)]]
}),
this.fb.group({
productName: ['AirPods Pro 2', Validators.required],
quantity: [1, [Validators.required, Validators.min(1)]],
price: [7500, [Validators.required, Validators.min(1)]]
})
])
});
// 幫助方法
get orderItems(): FormArray {
return this.form.get('orderItems') as FormArray;
}
addItem() {
this.orderItems.push(this.fb.group({
productName: [''],
quantity: [1],
price: [0]
}));
}
removeItem(i: number) {
this.orderItems.removeAt(i);
}
UI 綁定思路
<div formArrayName="orderItems">
<div *ngFor="let item of orderItems.controls; let i = index" [formGroupName]="i">
<input formControlName="productName" placeholder="商品名稱">
<input formControlName="quantity" type="number">
<input formControlName="price" type="number">
<button (click)="removeItem(i)">刪除</button>
</div>
</div>
<button (click)="addItem()">新增商品</button>
需求
程式碼
form = this.fb.group({
participants: this.fb.array([
this.fb.group({
name: ['王小明', Validators.required],
email: ['xiaoming@example.com', [Validators.required, Validators.email]]
}),
this.fb.group({
name: ['林小美', Validators.required],
email: ['xiaomei@example.com', [Validators.required, Validators.email]]
})
])
});
get participants(): FormArray {
return this.form.get('participants') as FormArray;
}
addParticipant() {
this.participants.push(this.fb.group({
name: [''],
email: ['']
}));
}
removeParticipant(i: number) {
this.participants.removeAt(i);
}
需求
程式碼
type SeatStatus = '一般' | '無障礙座位' | '維修中' | '無座位';
form = this.fb.group({
auditoriums: this.fb.array([
this.fb.group({
name: ['第1廳'],
showtimes: this.fb.array([
this.fb.group({
movieTitle: ['沙丘 2'],
startAt: ['2025-09-01T19:30:00+08:00'],
seats: this.fb.array([
// Row A
this.fb.array([
this.fb.group({ label: ['A1'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['A2'], status: ['無障礙座位' as SeatStatus] }),
this.fb.group({ label: ['A3'], status: ['維修中' as SeatStatus] }),
this.fb.group({ label: ['A4'], status: ['無座位' as SeatStatus] })
]),
// Row B
this.fb.array([
this.fb.group({ label: ['B1'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['B2'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['B3'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['B4'], status: ['一般' as SeatStatus] })
]),
// Row C
this.fb.array([
this.fb.group({ label: ['C1'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['C2'], status: ['無障礙座位' as SeatStatus] }),
this.fb.group({ label: ['C3'], status: ['一般' as SeatStatus] }),
this.fb.group({ label: ['C4'], status: ['一般' as SeatStatus] })
])
])
})
])
})
])
});
UI 綁定思路
auditoriums
:場廳清單showtimes
:場次清單seats
:二維陣列(Row → Col)ngFor
繪出座位表,並用 <select>
或顏色標籤顯示「一般 / 無障礙座位 / 維修中 / 無座位」網站示意:Day23
購物車 / 報名表
戲院座位表
最佳實務
controls
,盡量透過 getter 與 helper 方法。SeatRowComponent
)。👉 下一篇(篇四),我們將探討 跨元件巢狀與大表單拆分,讓大型表單不至於失控,維持良好維護性。