TL; DR
- 我更喜欢使用FormGroup填充复选框列表
- 编写自定义验证器以选中至少一个复选框
- 工作示例https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
这有时也让我感到震惊,因此我确实尝试了FormArray和FormGroup方法。
大多数情况下,复选框列表填充在服务器上,我是通过API收到的。但是有时您会看到一组带有预定义值的静态复选框。对于每个用例,将使用相应的FormArray或FormGroup。
基本上FormArray
是的变体FormGroup
。关键区别在于其数据被序列化为一个数组(与之相对的是在FormGroup中被序列化为一个对象)。当您不知道组中将存在多少控件(例如动态表单)时,这可能特别有用。
为了简单起见,假设您有一个简单的create product表单,
- 一个必需的产品名称文本框。
- 要选择的类别列表,要求至少要检查一个类别。假定将从服务器检索列表。
首先,我建立了一个仅具有产品名称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;
};
}