TL; DR
- Saya lebih suka menggunakan FormGroup untuk mengisi daftar kotak centang
- Tulis validator khusus untuk memeriksa setidaknya satu kotak centang dipilih
- Contoh kerja https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
Ini juga mengejutkan saya untuk kadang-kadang jadi saya mencoba pendekatan FormArray dan FormGroup.
Sebagian besar waktu, daftar kotak centang diisi di server dan saya menerimanya melalui API. Tetapi terkadang Anda akan memiliki sekumpulan kotak centang statis dengan nilai yang telah Anda tentukan sebelumnya. Dengan setiap kasus penggunaan, FormArray atau FormGroup yang sesuai akan digunakan.
Pada dasarnya FormArray
adalah varian dari FormGroup
. Perbedaan utamanya adalah bahwa datanya diserialkan sebagai array (sebagai lawan menjadi serial sebagai objek dalam kasus FormGroup). Ini mungkin berguna terutama ketika Anda tidak mengetahui berapa banyak kontrol yang akan ada dalam grup, seperti formulir dinamis.
Demi kesederhanaan, bayangkan Anda memiliki bentuk produk buat sederhana dengan
- Satu kotak teks nama produk yang diperlukan.
- Daftar kategori untuk dipilih, membutuhkan setidaknya satu untuk diperiksa. Asumsikan daftar tersebut akan diambil dari server.
Pertama, saya menyiapkan formulir hanya dengan nama produk formControl. Ini adalah bidang yang wajib diisi.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Karena kategori dirender secara dinamis, saya harus menambahkan data ini ke dalam formulir nanti setelah data siap.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Ada dua pendekatan untuk menyusun daftar kategori.
1. Bentuk Array
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>
Ini buildCategoryFormGroup
akan mengembalikan saya sebuah FormArray. Ini juga mengambil daftar nilai yang dipilih sebagai argumen jadi Jika Anda ingin menggunakan kembali formulir untuk mengedit data, ini bisa membantu. Untuk tujuan membuat bentuk produk baru, ini belum bisa diterapkan.
Tercatat ketika Anda mencoba mengakses nilai formArray. Ini akan terlihat seperti [false, true, true]
. Untuk mendapatkan daftar id yang dipilih, dibutuhkan sedikit lebih banyak pekerjaan untuk memeriksa dari daftar tetapi berdasarkan indeks array. Kedengarannya tidak bagus bagi saya, tetapi berhasil.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Itulah mengapa saya datang menggunakan FormGroup
masalah itu
2. Bentuk Kelompok
Perbedaan dari formGroup adalah akan menyimpan data form sebagai objek, yang membutuhkan key dan form control. Jadi adalah ide yang bagus untuk menyetel kunci sebagai categoryId dan kemudian kita bisa mengambilnya nanti.
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>
Nilai grup formulir akan terlihat seperti ini:
{
"category1": false,
"category2": true,
"category3": true,
}
Namun paling sering kami hanya ingin mendapatkan daftar categoryIds sebagai ["category2", "category3"]
. Saya juga harus menulis untuk mengambil data ini. Saya lebih menyukai pendekatan ini dibandingkan dengan formArray, karena saya sebenarnya dapat mengambil nilai dari formulir itu sendiri.
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. Validator khusus untuk memeriksa setidaknya satu kotak centang dipilih
Saya membuat validator untuk memeriksa setidaknya X kotak centang dipilih, secara default hanya akan memeriksa satu kotak centang.
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;
};
}