34

I want to do validation for checkboxes here without form tag. At least one checkbox should be selected.

<div *ngFor="let item of officeLIST">
  <div *ngIf=" item.officeID == 1">
    <input #off type="checkbox" id="off" name="off" value="1" [(ngModel)]="item.checked">
    <label>{{item.officename}}</label>
  </div>

  <div *ngIf="item.officeID== 2">
    <input #off type="checkbox" id="off" name="off" value="2" [(ngModel)]="item.checked">
    <label>{{item.officename}}</label>
  </div>

  <div *ngIf="item.officeID== 3">
    <input #off type="checkbox" id="off" name="off" value="3" [(ngModel)]="item.checked">
    <label>{{item.officename}}</label>
  </div>
</div>

for other field I will put required and do the error|touched|valid etc. but since checkbox is not single input, I cannot put required in every checkbox because all checkbox will be compulsory to checked. so how do I do the validation to alert user atleast one should be checked?

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
user3431310
  • 719
  • 1
  • 10
  • 30
  • you are not using any form tags, by validation when you want to validate? like `on click of some button should say nothing selected` – Aravind Apr 13 '17 at 06:35
  • I want to validate when there is no single checkbox is checked. for eg: name: textbox, when user clicked on it but did not enter anything then, should display'name is required'. same goes to checkbox – user3431310 Apr 13 '17 at 06:39

7 Answers7

81

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

Mick
  • 8,203
  • 10
  • 44
  • 66
  • 6
    one thing: `{ validators: requireCheckboxesToBeCheckedValidator() }` - the rest is ok – Sergej Mar 14 '19 at 13:07
  • @Sergej [FormGroup](https://angular.io/api/forms/FormGroup) constructor accepts `ValidatorFn` or `ValidatorFn[]`. Or what do you mean? – Mick Mar 18 '19 at 09:58
  • I mean that the validators must be an object with the `validators` key. You have missed it in your example. The 2nd argument in the `FormGroup` construcor. https://angular.io/api/forms/FormGroup You must be mixed up it with the `FormControl` which takes the validator itself as a parameter. – Sergej Mar 18 '19 at 14:04
  • That is only partially right. The definition of FormGroup says the second argument is of type `ValidatorFn | AbstractControlOptions | ValidatorFn[]`. So yes, you can use `AbstractControlOptions` as you mentioned, OR you can use a `ValidatorFn` directly as I did, OR an array of `ValidatorFn` – Mick Mar 18 '19 at 15:50
  • It is not working, if you pass the function as you did. I have tested, thats why I wrote about this here... or may be it could work, if you would not call() it, just pass the name of the function (this way I did not test) – Sergej Mar 19 '19 at 10:48
  • I posted a working example on stackblitz which confirms it is working + the documentation says there are this 3 options. The function has to be called to set how many checkboxes need to be checked and it returns another function so everything is fine. – Mick Mar 20 '19 at 10:02
  • Oh. yes, you are correct. I apologize. I were using the function as an object (wrong syntaxis) `{ requireCheckboxesToBeCheckedValidator() }`, I had to remove the `{}`. All works ok. – Sergej Mar 20 '19 at 14:26
  • however in my code, if use it with correct syntaxis, the validator it self is not applied to the form group. why can this happen? i console.log the group, it says `validators: null`. code: `this.fb.group( positions.map(position => { return this.fb.control(position.selected); }), requireCheckedValidator(1) );` but if I replace `requireCheckedValidator(1)` with `{ validators: requireCheckedValidator(1) }` then it works ok and validator is applied. – Sergej Mar 20 '19 at 14:38
  • What you return does not make sense to me: `this.fb.control(position.selected); }), requireCheckedValidator(1)`. Also i would rather need a full example to help. – Mick Mar 21 '19 at 15:08
  • `this.fb` - is the `FormBuilder`. S,o it returns `new FormControl(false)` or `new FormControl(true)`, depending on the `position.selected` value (actually means checkbox is checked or no) – Sergej Mar 21 '19 at 16:01
  • 2
    In case anyone has a need of adapting this solution to work with a dynamic list of checkboxes the trick is to add the inner FormGroup inside the subscribe call. Like: `getCheckboxes().subscribe(result => { const formControls = {}; result.forEach(i => formControls['myCheckbox' + i.id] = new FormControl(false)); this.form.addControl('myCheckboxGroup', new FormGroup(formControls, requireCheckboxesToBeCheckedValidator())); }` – Lee Richardson Mar 28 '19 at 12:50
  • can give suggestion and soultion for my problem ? ....https://stackoverflow.com/questions/57895884/disable-formgroup-by-using-validators – hafizi hamid Sep 12 '19 at 07:49
22

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 formcontrol
checkboxGroup.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.

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
Pengyy
  • 37,383
  • 15
  • 83
  • 73
  • Better answer IMHO – Lereveme Aug 10 '17 at 21:52
  • 1
    Hi thanks for this answer, but I'm just curious - is it necessary to instantiate 3 FormControls for every checkbox in order to create a "required" validateable (dont think thats a word) checkbox list? This definitely seems to work and I'm going to mess with it a little bit, but I feel like there's some unnecessary overhead here... what do you think? – Adam Plocher Nov 03 '18 at 09:11
  • @AdamPlocher we need to show three checkboxes, I think there is no simple way for that. – Pengyy Nov 03 '18 at 11:01
  • Yeah but, you're creating 3 instances per checkbox, right (3 checkboxes - 9 instances)? With this line you create a FormControl for the id, text, and checkbox itself I think. It may be virtually no overhead at all, I just think there's gotta be a better way... Perhaps not though, I'm no expert in Angular 2 - 6 :) ... `new FormArray(this.items.map(item => new FormGroup({ id: new FormControl(item.key), text: new FormControl(item.text), checkbox: new FormControl(false)})));` – Adam Plocher Nov 04 '18 at 08:03
  • 1
    @AdamPlocher Sorry for incorrect response before, I was able to think about your question right now. The reason for `3 instance per checkbox` here is that `FormControl` itself doesn't support for keeping additional info such as id and text for the checkbox. – Pengyy Nov 07 '18 at 01:34
  • 5
    @AdamPlocher You should not abuse a FormControl to store data as their value(here the key and label). Thats a very wrong way to think about reactive forms in Angular. A FormGroup holds FormControls with their validators, current value and current status and nothing more. This is not a good answer. The general idea of creating an additonal, hidden, checkbox (Step2) is not too bad tho, but it is way easier to create a validator for the whole FormGroup. No need to watch valueChanges. Also btw. having it required is wrong it should have the validator requiredTrue. – Mick Jan 04 '19 at 08:51
  • 2
    Please see [my answer](https://stackoverflow.com/a/54036689/5688490) for that solution – Mick Jan 04 '19 at 10:03
  • @Pengyy Could you please delete your answer as it shows a wrong way of using FormControls – Mick Jun 27 '19 at 13:06
  • @Mick I don't think he should delete it. It's not ideal, sure, but I wouldn't say it's "wrong" either. The OP should simply reconsider the accepted answer is all. – crush Jul 26 '19 at 15:59
8

I had the same problem and this is the solution I ended up using with Angular 6 FormGroup because I had few checkboxes.

HTML Note: 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.

eper
  • 1,043
  • 10
  • 10
  • 2
    It is way easier to just give the whole FormGroup a validator that checks if at least one checkbox is checked. No need for watching valueChanges. – Mick Jan 04 '19 at 08:49
  • 1
    @Mick After looking at your answer I agree with you, it was the solution I came up with at the time. I have upvoted your answer as it looks to be more efficient and reusable and will use that model in future development as I think it's the best answer IMHO. Thanks! – eper Feb 14 '19 at 00:16
3

On validation (i.e for example some click event) iterate over your array and check whether at least one item is true.

let isSelected: any = this.officeLIST.filter((item) => item.checked === true);
if(isSelected != null && isSelected.length > 0) {
 //At least one is selected
}else {
 alert("select at least one");
}
Nugu
  • 852
  • 7
  • 10
1

Add (ngModelChange)="onChange(officeLIST)" to your checkbox and have below code in your .ts file.

onChange(items) {
    var found = items.find(function (x) { return x.checked === true; });
    if (found)
      this.isChecked = true;
    else
      this.isChecked = false;
  }

Use isChecked variable any places you want.

0

I implemented a similar solution to the current accepted version proposed by Mick(using FormGroup and a custom Validator), but if you're like me and aren't going to need to handle showing an error for quantities checked > 0, you can simplify the Validator a lot:

export function checkboxGroupValidator(): ValidatorFn {
  return (formGroup: FormGroup) => {
    const checkedKeys = Object.keys(formGroup.controls).filter((key) => formGroup.controls[key].value);

    if (checkedKeys.length === 0) { return { requireCheckboxesToBeChecked: true }; }

    return null;
  };
}
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/30142307) – AthulMuralidhar Oct 21 '21 at 18:13
-2

You should be checking the touched and dirty conditions of the form element

<form #myForm="ngForm"  *ngIf="active"  (ngSubmit)="onSubmit()">

    <div class="form-group">
        <label for="name">Name</label>
        <input type="text" id="name" class="form-control"
               required name="name" [(ngModel)]="myform.name"
               #name="ngModel" >
        <div *ngIf="name.errors && (name.dirty || name.touched)"
             class="alert alert-danger">
            <div [hidden]="!name.errors.required">
              Name is required
            </div>
        </div>
    </div>

</form>

You can combine the previous and my answer for both scenarios

Aravind
  • 40,391
  • 16
  • 91
  • 110