TL; ดร
- ฉันชอบใช้ FormGroup เพื่อเติมรายการช่องทำเครื่องหมาย
- เขียนตัวตรวจสอบความถูกต้องที่กำหนดเองสำหรับการตรวจสอบอย่างน้อยหนึ่งช่องทำเครื่องหมายถูกเลือก
- ตัวอย่างการทำงานhttps://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
บางครั้งสิ่งนี้ก็ทำให้ฉันรู้สึกเช่นกันดังนั้นฉันจึงลองใช้ทั้ง FormArray และ FormGroup
โดยส่วนใหญ่แล้วรายการช่องทำเครื่องหมายจะปรากฏบนเซิร์ฟเวอร์และฉันได้รับผ่าน API แต่บางครั้งคุณจะมีชุดช่องทำเครื่องหมายคงที่พร้อมค่าที่กำหนดไว้ล่วงหน้า ในแต่ละกรณีการใช้งานจะใช้ FormArray หรือ FormGroup ที่เกี่ยวข้อง
โดยทั่วไปFormArray
เป็นตัวแปรของFormGroup
. ความแตกต่างที่สำคัญคือข้อมูลจะถูกทำให้เป็นอนุกรมเป็นอาร์เรย์ (ตรงข้ามกับการทำให้เป็นอนุกรมเป็นวัตถุในกรณีของ FormGroup) สิ่งนี้อาจมีประโยชน์อย่างยิ่งเมื่อคุณไม่รู้ว่าจะมีตัวควบคุมจำนวนเท่าใดในกลุ่มเช่นฟอร์มไดนามิก
เพื่อความเรียบง่ายลองจินตนาการว่าคุณสร้างรูปแบบผลิตภัณฑ์ง่ายๆด้วย
- กล่องข้อความชื่อผลิตภัณฑ์ที่ต้องการหนึ่งกล่อง
- รายการหมวดหมู่ที่จะเลือกต้องเลือกอย่างน้อยหนึ่งรายการ สมมติว่ารายการจะถูกดึงมาจากเซิร์ฟเวอร์
ก่อนอื่นฉันตั้งค่าแบบฟอร์มด้วยชื่อผลิตภัณฑ์ formControl เท่านั้น เป็นฟิลด์บังคับ
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
เนื่องจากหมวดหมู่นี้แสดงผลแบบไดนามิกฉันจะต้องเพิ่มข้อมูลเหล่านี้ลงในแบบฟอร์มในภายหลังหลังจากที่ข้อมูลพร้อม
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
มีสองวิธีในการสร้างรายการหมวดหมู่
1. ฟอร์มอาร์เรย์
buildCategoryFormArr(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormArray {
const controlArr = categories.map(category => {
let isSelected = selectedCategoryIds.some(id => id === category.id);
return this.formBuilder.control(isSelected);
})
return this.formBuilder.array(controlArr, atLeastOneCheckboxCheckedValidator())
}
<div *ngFor="let control of categoriesFormArr?.controls; let i = index" class="checkbox">
<label><input type="checkbox" [formControl]="control" />
{{ categories[i]?.title }}
</label>
</div>
สิ่งนี้buildCategoryFormGroup
จะคืน FormArray ให้ฉัน นอกจากนี้ยังใช้รายการค่าที่เลือกเป็นอาร์กิวเมนต์ดังนั้นหากคุณต้องการใช้แบบฟอร์มนี้ซ้ำเพื่อแก้ไขข้อมูลอาจเป็นประโยชน์ สำหรับวัตถุประสงค์ในการสร้างรูปแบบผลิตภัณฑ์ใหม่ยังไม่สามารถใช้งานได้
สังเกตว่าเมื่อคุณพยายามเข้าถึงค่า formArray จะมีลักษณะ[false, true, true]
ดังนี้ ในการรับรายการ id ที่เลือกต้องใช้งานอีกเล็กน้อยในการตรวจสอบจากรายการ แต่ขึ้นอยู่กับดัชนีอาร์เรย์ ฟังดูไม่ดีสำหรับฉัน แต่มันใช้งานได้
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
นั่นเป็นเหตุผลที่ฉันใช้FormGroup
สำหรับเรื่องนั้น
2. กลุ่มฟอร์ม
formGroup ที่แตกต่างกันคือจะเก็บข้อมูลฟอร์มเป็นวัตถุซึ่งต้องใช้คีย์และตัวควบคุมฟอร์ม ดังนั้นจึงเป็นความคิดที่ดีที่จะตั้งค่าคีย์เป็น categoryId แล้วเราสามารถเรียกดูได้ในภายหลัง
buildCategoryFormGroup(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormGroup {
let group = this.formBuilder.group({}, {
validators: atLeastOneCheckboxCheckedValidator()
});
categories.forEach(category => {
let isSelected = selectedCategoryIds.some(id => id === category.id);
group.addControl(category.id, this.formBuilder.control(isSelected));
})
return group;
}
<div *ngFor="let item of categories; let i = index" class="checkbox">
<label><input type="checkbox" [formControl]="categoriesFormGroup?.controls[item.id]" /> {{ categories[i]?.title }}
</label>
</div>
ค่าของกลุ่มฟอร์มจะมีลักษณะดังนี้:
{
"category1": false,
"category2": true,
"category3": true,
}
แต่ส่วนใหญ่เรามักจะต้องการที่จะได้รับเพียงรายการ categoryIds ["category2", "category3"]
เป็น ฉันต้องเขียนวิธีรับข้อมูลเหล่านี้ด้วย ฉันชอบวิธีนี้มากกว่าเมื่อเปรียบเทียบกับ formArray เพราะฉันสามารถรับค่าจากแบบฟอร์มได้
get categoriesFormGroupSelectedIds(): string[] {
let ids: string[] = [];
for (var key in this.categoriesFormGroup.controls) {
if (this.categoriesFormGroup.controls[key].value) {
ids.push(key);
}
else {
ids = ids.filter(id => id !== key);
}
}
return ids;
}
3. กำหนดตัวตรวจสอบความถูกต้องเพื่อตรวจสอบอย่างน้อยหนึ่งช่องทำเครื่องหมายถูกเลือก
ฉันทำให้ตัวตรวจสอบความถูกต้องเพื่อตรวจสอบอย่างน้อยช่องทำเครื่องหมาย X ถูกเลือกโดยค่าเริ่มต้นจะตรวจสอบกับช่องทำเครื่องหมายเดียวเท่านั้น
export function atLeastOneCheckboxCheckedValidator(minRequired = 1): ValidatorFn {
return function validate(formGroup: FormGroup) {
let checked = 0;
Object.keys(formGroup.controls).forEach(key => {
const control = formGroup.controls[key];
if (control.value === true) {
checked++;
}
});
if (checked < minRequired) {
return {
requireCheckboxToBeChecked: true,
};
}
return null;
};
}