How to validate that at least one checkbox should be selected?
The accepted answer abuses stuff to use in a way they are not meant to be. With reactive forms the best, easiest and probably right way is to use a FormGroup that holds your grouped checkboxes and create a validator to check if at least one(or more) checkbox is checked within that group.
To do so just create another FormGroup
inside your existing FormGroup
and attach a validator to it:
form = new FormGroup({ // ...more form controls... myCheckboxGroup: new FormGroup({ myCheckbox1: new FormControl(false), myCheckbox2: new FormControl(false), myCheckbox3: new FormControl(false), }, requireCheckboxesToBeCheckedValidator()), // ...more form controls... });
And here is the validator. I made it so you can even use it to check if at least X checkboxes are checked, e.g. requireCheckboxesToBeCheckedValidator(2)
:
import { FormGroup, ValidatorFn } from '@angular/forms';export function requireCheckboxesToBeCheckedValidator(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 { requireCheckboxesToBeChecked: true, }; } return null; };}
In your template don't forget to add the directive 'formGroupName' to wrap your checkboxes. But don't worry, the compiler will remind you with an error-message if you forget. You can then check if the checkbox-group is valid the same way you do on FormControl's:
<ng-container [formGroup]="form"> <!-- ...more form controls... --> <div class="form-group" formGroupName="myCheckboxGroup"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" formControlName="myCheckbox1" id="myCheckbox1"> <label class="custom-control-label" for="myCheckbox1">Check</label> </div> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" formControlName="myCheckbox2" id="myCheckbox2"> <label class="custom-control-label" for="myCheckbox2">At least</label> </div> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" formControlName="myCheckbox3" id="myCheckbox3"> <label class="custom-control-label" for="myCheckbox3">One</label> </div> <div class="invalid-feedback" *ngIf="form.controls['myCheckboxGroup'].errors && form.controls['myCheckboxGroup'].errors.requireCheckboxesToBeChecked">At least one checkbox is required to check</div> </div> <!-- ...more form controls... --> </ng-container>
*This template is very static. Of course you could create it dynamically by using an additional array that holds the the form-data(key of FormControl, label, required, etc.) and create the template automatically by use of ngFor.
Please don't abuse hidden FormControl's like in the accepted answer. A FormControl is not meant to store data like id, label, help-text etc. and doesnt even have a name/key. All this, and much more, should be stored separate, e.g. by a regular array of objects. A FormControl only holds an input-value and provides all this cool state's and functions.
I created a working example you can play with: https://stackblitz.com/edit/angular-at-least-one-checkbox-checked
consider creating a FormGroup
which contains your check-box group and bind the group's checked value to a hidden formcontrol with a required validator.
Assume that you have three check boxes
items = [ {key: 'item1', text: 'value1'}, // checkbox1 (label: value1) {key: 'item2', text: 'value2'}, // checkbox2 (label: value2) {key: 'item3', text: 'value3'}, // checkbox3 (label: value3)];
Step1: define FormArray
for your check boxes
let checkboxGroup = new FormArray(this.items.map(item => new FormGroup({ id: new FormControl(item.key), // id of checkbox(only use its value and won't show in html) text: new FormControl(item.text), // text of checkbox(show its value as checkbox's label) checkbox: new FormControl(false) // checkbox itself})));
*easy to show via ngFor
Step2: create a hidden required formControl to keep status of checkbox group
let hiddenControl = new FormControl(this.mapItems(checkboxGroup.value), Validators.required);// update checkbox group's value to hidden formcontrolcheckboxGroup.valueChanges.subscribe((v) => { hiddenControl.setValue(this.mapItems(v));});
we only care about hidden control's required validate status and won't show this hidden control in html.
Step3: create final form group contains below checkbox group and hidden formControl
this.form = new FormGroup({ items: checkboxGroup, selectedItems: hiddenControl});
Html Template:
<form [formGroup]="form"> <div [formArrayName]="'items'" [class.invalid]="!form.controls.selectedItems.valid"> <div *ngFor="let control of form.controls.items.controls; let i = index;" [formGroup]="control"> <input type="checkbox" formControlName="checkbox" id="{{ control.controls.id.value }}"> <label attr.for="{{ control.controls.id.value }}">{{ control.controls.text.value }}</label> </div> </div> <div [class.invalid]="!form.controls.selectedItems.valid" *ngIf="!form.controls.selectedItems.valid"> checkbox group is required! </div> <hr> <pre>{{form.controls.selectedItems.value | json}}</pre></form>
refer this demo.
I had the same problem and this is the solution I ended up using with Angular 6 FormGroup because I had few checkboxes.
HTMLNote: I'm using Angular Material for styling, change as needed.
<form [formGroup]="form"> <mat-checkbox formControlName="checkbox1">First Checkbox</mat-checkbox> <mat-checkbox formControlName="checkbox2">Second Checkbox</mat-checkbox> <mat-checkbox formControlName="checkbox3">Third Checkbox</mat-checkbox></form>
TypeScript
form: FormGroup;constructor(private formBuilder: FormBuilder){}ngOnInit(){ this.form = this.formBuilder.group({ checkbox1: [''], checkbox2: [''], checkbox3: [''], }); this.form.setErrors({required: true}); this.form.valueChanges.subscribe((newValue) => { if (newValue.checkbox1 === true || newValue.checkbox2 === true || newValue.checkbox3 === true) { this.form.setErrors(null); } else { this.form.setErrors({required: true}); } });}
Basically, subscribe to any changes in the form and then modify the errors as needed according to the new form values.