TL; DR
- Onay kutusu listesini doldurmak için FormGroup kullanmayı tercih ediyorum
- En az bir onay kutusunun seçildiğini kontrol etmek için özel bir doğrulayıcı yazın
- Çalışma örneği https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
Bu da bazen beni etkiledi, bu yüzden hem FormArray hem de FormGroup yaklaşımlarını denedim.
Çoğu zaman, onay kutusu listesi sunucuda dolduruldu ve bunu API aracılığıyla aldım. Ancak bazen önceden tanımlanmış değerinizle birlikte statik bir onay kutusu kümesine sahip olursunuz. Her kullanım senaryosunda, karşılık gelen FormArray veya FormGroup kullanılacaktır.
Temelde FormArray
bir çeşididir FormGroup
. Temel fark, verilerinin bir dizi olarak serileştirilmesidir (FormGroup durumunda bir nesne olarak serileştirilmesinin aksine). Bu, grup içinde dinamik formlar gibi kaç kontrolün bulunacağını bilmediğinizde özellikle yararlı olabilir.
Basitlik uğruna, basit bir ürün formunuz olduğunu hayal edin.
- Gerekli bir ürün adı metin kutusu.
- En az birinin kontrol edilmesi gereken bir kategori listesi. Listenin sunucudan alınacağını varsayın.
İlk olarak, sadece formControl ürün adı ile bir form oluşturdum. Zorunlu bir alandır.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Kategori dinamik olarak oluşturulduğundan, bu verileri daha sonra veriler hazır olduktan sonra forma eklemem gerekecek.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Kategori listesini oluşturmak için iki yaklaşım vardır.
1. Form Dizisi
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>
Bu buildCategoryFormGroup
bana bir FormArray döndürecektir. Ayrıca bağımsız değişken olarak seçilen değerin bir listesini alır, bu nedenle verileri düzenlemek için formu yeniden kullanmak isterseniz, bu yardımcı olabilir. Yeni bir ürün formu oluşturmak amacıyla henüz uygulanabilir değildir.
FormArray değerlerine erişmeye çalıştığınızda dikkat edin. Gibi görünecek [false, true, true]
. Seçilen kimliğin bir listesini almak için, listeden kontrol etmek için biraz daha çalışma gerekiyordu, ancak dizi indeksine bağlıydı. Bana iyi gelmiyor ama işe yarıyor.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Bu yüzden FormGroup
bu konuyu kullanarak geldim
2. Form Grubu
FormGroup'tan farklı olan, form verilerini bir anahtar ve bir form denetimi gerektiren nesne olarak depolamasıdır. Bu nedenle, anahtarı kategori kimliği olarak ayarlamak iyi bir fikirdir ve daha sonra onu geri alabiliriz.
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>
Form grubunun değeri şöyle görünecektir:
{
"category1": false,
"category2": true,
"category3": true,
}
Ancak çoğu zaman yalnızca categoryIds listesini almak isteriz ["category2", "category3"]
. Ayrıca bu verileri almak için bir get yazmam gerekiyor. FormArray ile karşılaştırıldığında bu yaklaşımı daha çok seviyorum, çünkü değeri aslında formun kendisinden alabilirim.
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. En az bir onay kutusunu işaretlemek için özel doğrulayıcı seçildi
Doğrulayıcının en az X onay kutusunun seçili olduğunu kontrol etmesini sağladım, varsayılan olarak yalnızca bir onay kutusunu kontrol edecek.
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;
};
}