Just made an update to my solution by implementing the formControlName
. The explanation remains unchanged! However, I utilized .bind(this, data)
to transmit the dynamic data used in the *ngFor
to initialize the checkboxes. This is necessary to ensure that the values are checked, guaranteeing at least one checkbox is selected!
Validation function
export function ValidateCheckboxes(data: any, control: AbstractControl) {
console.log(data, control.value.cb);
if (
!data.some(
(item: any, index: number) => control.value.cb[index][item.id]
)
) {
return { checkboxSectionValid: true };
}
return null;
}
Check out the stackblitz demo
To simplify managing the checkboxes, we can utilize a formArray
to keep all inputs under the same formArray control.
this.formGroup = this.formBuilder.group({
cb: this.formBuilder.array([]),
});
const cb: FormArray = this.cbArray;
this.data.forEach((item: any) => {
cb.push(new FormControl(null));
});
A custom validator is required to verify if any of the checkboxes are ticked off. Here's how you can set it up:
this.formGroup.setValidators(ValidateCheckboxes);
Validator function
export function ValidateCheckboxes(control: AbstractControl) {
console.log(control.value.cb);
if (!control.value.cb.some((item: any) => item)) {
return { checkboxSectionValid: true };
}
return null;
}
In your HTML, group all checkboxes within a single formGroupName
, then attach the formArray controls to each checkbox. To display an error message for unchecked boxes, use
*ngIf="formGroup?.errors?.checkboxSectionValid"
.
<div formArrayName="cb">
<label *ngFor="let val of cbArray.controls; let i = index"
><input
type="checkbox"
name="{{ val.name }}"
id="{{ val.id }}"
[formControl]="val"
/>
{{ data[i].value }}</label
>
</div>
<div
style="color: red; padding-top: 0.2rem"
*ngIf="formGroup?.errors?.checkboxSectionValid"
>
Atleast select one checkbox
</div>
Complete code snippet
import { Component, OnInit, VERSION } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
FormArray,
FormControl,
} from '@angular/forms';
export function ValidateCheckboxes(control: AbstractControl) {
console.log(control.value.cb);
if (!control.value.cb.some((item: any) => item)) {
return { checkboxSectionValid: true };
}
return null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
formGroup: FormGroup;
public data = [
{
name: 'chk1',
id: 'chk1',
value: 'Car',
},
{
name: 'chk2',
id: 'chk2',
value: 'Bus',
},
{
name: 'chk3',
id: 'chk4',
value: 'Motor',
},
];
constructor(private readonly formBuilder: FormBuilder) {}
get cbArray() {
return this.formGroup.get('cb') as FormArray;
}
ngOnInit(): void {
this.formGroup = this.formBuilder.group({
cb: this.formBuilder.array([]),
});
const cb: FormArray = this.cbArray;
this.data.forEach((item: any) => {
cb.push(new FormControl(null));
});
this.formGroup.setValidators(ValidateCheckboxes);
}
}
HTML markup
<div style="padding: 1rem">
<form [formGroup]="formGroup">
<div formArrayName="cb">
<label *ngFor="let val of cbArray.controls; let i = index"
><input
type="checkbox"
name="{{ val.name }}"
id="{{ val.id }}"
[formControl]="val"
/&ft;
{{ data[i].value }}</label
>
</div>
<div
style="color: red; padding-top: 0.2rem"
*ngIf="formGroup?.errors?.checkboxSectionValid"
>
Atleast select one checkbox
</div>
<hr />
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
Preview the stackblitz demo