TL; DR
- Ich bevorzuge die Verwendung von FormGroup, um die Liste der Kontrollkästchen zu füllen
- Schreiben Sie einen benutzerdefinierten Validator, um zu überprüfen, ob mindestens ein Kontrollkästchen aktiviert wurde
- Arbeitsbeispiel https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
Dies fiel mir auch manchmal auf, so dass ich sowohl FormArray- als auch FormGroup-Ansätze ausprobierte.
Die Liste der Kontrollkästchen wurde die meiste Zeit auf dem Server ausgefüllt und ich habe sie über die API erhalten. Manchmal haben Sie jedoch ein statisches Kontrollkästchen mit Ihrem vordefinierten Wert. In jedem Anwendungsfall wird das entsprechende FormArray oder FormGroup verwendet.
Grundsätzlich FormArray
ist eine Variante von FormGroup
. Der Hauptunterschied besteht darin, dass seine Daten als Array serialisiert werden (im Gegensatz zu einer Serialisierung als Objekt bei FormGroup). Dies kann besonders nützlich sein, wenn Sie nicht wissen, wie viele Steuerelemente in der Gruppe vorhanden sein werden, z. B. dynamische Formulare.
Stellen Sie sich der Einfachheit halber vor, Sie hätten ein einfaches Produktformular zum Erstellen
- Ein erforderliches Textfeld für den Produktnamen.
- Für eine Liste der Kategorien, aus denen Sie auswählen können, muss mindestens eine überprüft werden. Angenommen, die Liste wird vom Server abgerufen.
Zuerst habe ich ein Formular mit nur dem Produktnamen formControl eingerichtet. Es ist ein Pflichtfeld.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Da die Kategorie dynamisch gerendert wird, muss ich diese Daten später in das Formular einfügen, nachdem die Daten fertig waren.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Es gibt zwei Ansätze, um die Kategorieliste aufzubauen.
1. Formulararray
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>
Dies buildCategoryFormGroup
gibt mir ein FormArray zurück. Es wird auch eine Liste ausgewählter Werte als Argument verwendet. Wenn Sie das Formular zum Bearbeiten von Daten wiederverwenden möchten, kann dies hilfreich sein. Für die Erstellung eines neuen Produktformulars ist es noch nicht anwendbar.
Beachten Sie dies, wenn Sie versuchen, auf die formArray-Werte zuzugreifen. Es wird so aussehen [false, true, true]
. Um eine Liste der ausgewählten IDs zu erhalten, war etwas mehr Arbeit erforderlich, um sie anhand der Liste zu überprüfen, jedoch basierend auf dem Array-Index. Klingt für mich nicht gut, aber es funktioniert.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Deshalb bin ich auf FormGroup
diese Sache gekommen
2. Formulargruppe
Der Unterschied der formGroup besteht darin, dass die Formulardaten als Objekt gespeichert werden, für das ein Schlüssel und ein Formularsteuerelement erforderlich sind. Es ist daher eine gute Idee, den Schlüssel als Kategorie-ID festzulegen, damit wir ihn später abrufen können.
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>
Der Wert der Formulargruppe sieht folgendermaßen aus:
{
"category1": false,
"category2": true,
"category3": true,
}
Meistens möchten wir jedoch nur die Liste der Kategorie-IDs als erhalten ["category2", "category3"]
. Ich muss auch ein Get schreiben, um diese Daten zu nehmen. Ich mag diesen Ansatz besser im Vergleich zum formArray, weil ich den Wert tatsächlich aus dem Formular selbst übernehmen könnte.
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. Benutzerdefinierter Validator zum Aktivieren mindestens eines Kontrollkästchens wurde aktiviert
Ich habe den Validator so eingestellt, dass mindestens das Kontrollkästchen X aktiviert ist. Standardmäßig wird nur ein Kontrollkästchen aktiviert.
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;
};
}