TL; DR
- チェックボックスのリストを入力するためにFormGroupを使用する
- 少なくとも1つのチェックボックスが選択されていることを確認するためのカスタムバリデーターを作成する
- 動作例https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
これも時々私を襲ったので、私はFormArrayとFormGroupの両方のアプローチを試しました。
ほとんどの場合、チェックボックスのリストはサーバーに入力され、APIを介してそれを受け取りました。ただし、事前定義された値を持つチェックボックスの静的セットがある場合があります。ユースケースごとに、対応するFormArrayまたはFormGroupが使用されます。
基本的にFormArray
はのバリアントですFormGroup
。主な違いは、データが配列としてシリアル化されることです(FormGroupの場合はオブジェクトとしてシリアル化されるのとは対照的です)。これは、動的フォームのように、グループ内に存在するコントロールの数がわからない場合に特に役立ちます。
簡単にするために、単純な製品作成フォームがあるとします。
- 1つの必須の製品名テキストボックス。
- 選択するカテゴリのリスト。少なくとも1つはチェックする必要があります。リストがサーバーから取得されると仮定します。
まず、製品名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));
})
カテゴリリストを作成するには、2つの方法があります。
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,
}
しかし、ほとんどの場合、categoryIdのリストのみを取得する必要があります["category2", "category3"]
。これらのデータを取得するためのgetも作成する必要があります。実際にフォーム自体から値を取得できるため、この方法は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.少なくとも1つのチェックボックスをチェックするカスタムバリデーターが選択されました
少なくともXチェックボックスをオンにするようにバリデーターを選択しました。デフォルトでは、1つのチェックボックスのみをチェックします。
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;
};
}