I'm using Angular 13 and I'm trying to apply a custom validator for some of the fields of a form. Lets say I have:
- Input 1
- Input 2
- Checkbox 1
- Checkbox 2
- Checkbox 3
in which I bind an ngForm and a bidirectional ngModel in every field:
<form #inspectionForm="ngForm">
...
...
...
<div >
<input type="text " [(ngModel)]="inspectionService.checkbox1" name="checkbox1" required />
</div>
</form>
<button [disabled]="!inspectionForm.form.valid" type="button" (click)="submit();">Continue</button>
I want to apply the validator to the checkboxes only. It must force the user to check at least one.
The point is to disable a button by using [disabled]="!inspectionForm.form.valid"
This is essencially my code in the form.component.ts
:
@ViewChild('inspectionForm') inspectionForm ?: NgForm;
atLeastOneValidator(checkboxes: string[]): ValidatorFn{
return (control: AbstractControl): { [key: string]: boolean } | null => {
for(let i = 0; i < checkboxes.length; i ){
if(this.inspectionForm!.form.get(checkboxes[i])?.value === true) return null;
}
return { 'atLeastOne': true };
}
}
ngOnInit(): void {
let checkboxes = ['checkbox1', 'checkbox2', 'checkbox3']; // Checkboxes' names
checkboxes.forEach(item => {
this.inspectionForm?.form.controls[item].setValidators([this.atLeastOneValidator(checkboxes)]);
this.inspectionForm?.form.controls[item].updateValueAndValidity({onlySelf: true});
})
but it doesn't work. I don't know what am I doing wrong or if I skipped something
CodePudding user response:
A multi-control validatino should be made on the form, not on a control.
form = this.formBuilder.group({ /* ... */ }, [atLeastOne(['cb1'])]);
// Some file somewhere
export function atLeastOne(boxNames) {
return function(form) {
const boxes = Object.entries(form.controls)
.filter(([key]) => boxNames.includes(key))
.map(([, v]) => v);
const valid = boxes.some(box => !!box.value);
return valid ? null : { 'atleastone': true };
}
}
CodePudding user response:
The problem is that "this" has no value into your function atLeastOneValidator
You can use bind(this)
(it's javascript bind)
this.inspectionForm?.form.controls[item].setValidators(
[this.atLeastOneValidator(checkboxes).bind(this)]);
but be sure you get the form inside your function atLeastOneValidator
Or "reach" the form using control.parent
atLeastOneValidator(checkboxes: string[]): ValidatorFn {
return (control: AbstractControl): { [key: string]: boolean } | null => {
const parent = control.parent as FormGroup;
if (!parent)
return null;
const success = checkboxes.reduce((a,b)=>a || parent.get(b).value,false);
return success ? null : { atLeastOne: true };
};
}
But be carefull, a FormControl only is checked if valid or not when have a input relationated and the input change or when we call manually to updateValueAndValidity.
So, we can create a function
updateValueAndValidity(...checkboxes:string[])
{
checkboxes.forEach((x) => {
this.form.get(x).updateValueAndValidity({emitEvent:false})
});
}
And call in .html
<form [formGroup]="form">
<input type="checkbox" formControlName="checkbox1"
(change)="updateValueAndValidity('checkbox2','checkbox3')"/>
<input type="checkbox" formControlName="checkbox2"
(change)="updateValueAndValidity('checkbox1','checkbox3')"/>
<input type="checkbox" formControlName="checkbox3"
(change)="updateValueAndValidity('checkbox1','checkbox2')"/>
</form>
Or subscribe to valuesChanges