3

How can I do the validation of checking that at least 1 checkbox is selected from a checkbox list in Angular 2 template forms?

HTML code.

   <div class="form-control" style="max-height: 300px; overflow: auto">
        <div *ngFor="let item of items" class="checkbox">
            <label for="{{item.id}}">
                <input type="checkbox" value="{{item.id}}" required id="{{item.id}}" [(ngModel)]="item.isSelected" />
                <span class="text-body">{{item.name}}</span>
            </label>
        </div>
   </div>
   <div *ngIf="!isAtleastOneItemSelected()" class="alert alert-error">
        Select at least one item
   </div>

Component Code.

public isAtleastOneItemSelected() {
    const selectedItems = this.items.filter((item) => item.isSelected);
    if (selectedItems.length > 0) {
        return true;
    } else {
        return false;
    }
}

I have already checked the Reactive form way of doing this here. So, wanted to check how we can do this in Template Forms. Using the code that I have pasted above, things are working fine but on page load the error message is showing up as there is no check for dirty or touched there. I was stuck with this issue.

Any help on this would be appreciated.

Thanks.

RKS
  • 1,370
  • 11
  • 20
  • Me too, I spent some time on this and it seems impossible :( form.valid is always true – Vega Aug 16 '17 at 00:05
  • No chance you can do it with normal ngModel and custom validator. Because you have a list of checkbox, how would you know that they are related together? You have to use formGroup to group them and then do a custom validator for that with the Reactive Forms. Working example https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected – Sasuke91 Jun 18 '19 at 02:41

2 Answers2

1

Well I see two viable options here. I would go with the first, just because I would suggest to avoid any methods in the template, because you cannot restrict when and how often the method will be fired. Especially if you have a view with many ways to modify data in some way, the method will be fired even though it has nothing to do with the actual checkboxes. Look for a sample later.

So what I would do, is to change the

*ngIf="!isAtleastOneItemSelected()"

to check a variable instead:

*ngIf="nonChecked"

and have some type of change event when checkboxes are clicked, which fires some method, e.g:

check() {
  const selectedItems = this.items.filter((item) => item.isSelected === true);
  if (selectedItems.length > 0) {
     this.nonChecked = false;
  } else {
     this.nonChecked = true;
  }    
}

Now this will only be fired when ever the checkboxes are touched.


The other option I see, if you want to keep the similar setup, is to wrap the checkboxes in a formgroup and watch for when the group is touched and only then show the error message if there's no item checked. So you would wrap the checkboxes inside a group:

<div ngModelGroup="checkboxes">
  <!-- .... -->
</div>

and then you can do:

<div *ngIf="!isAtleastOneItemSelected() && f.controls?.checkboxes?.dirty">
  Select at least one item
</div>  

where f is the name of the form. But as earlier mentioned, this will fire on change detections and doing anything else in the DOM. Here is a sample of this solution, and where we only have one other field in DOM (see the console), so if you have lots of stuff going on, I'd suggest the first solution :) But either way, both works!

http://plnkr.co/edit/VuLNR8H3lasFoK7mRDlG?p=preview

AT82
  • 71,416
  • 24
  • 140
  • 167
0

Look at this, you will need to adapt:

<div *ngFor="let item of items; let i = index" class="checkbox"> 
        <label for="{{item.id}}">
            <input type="checkbox" value="{{item.id}}" required id="{{item.id}}" [(ngModel)]="item.isSelected" (change)="CheckBoxChanged(i,$event)"/>
            <span class="text-body">{{item.name}}</span>
        </label>
 </div>
   <div [hidden]="isAtleastOneItemSelected()" class="alert alert-error">
        Select at least one item
   </div>

put your check logic here...:

CheckBoxChanged(i,e){
    console.log(i);
    if (e.target.checked)
       console.log("checked");
    else 
       console.log("unchecked");
}
sancelot
  • 1,905
  • 12
  • 31
  • How does this prevent the initial error message from getting displayed i.e. when the control is not dirty or touched? – RKS Aug 15 '17 at 21:01
  • Still, on the initial page load, this error message would still be displayed as none of the associated models would be checked. – RKS Aug 15 '17 at 23:09
  • Can you please create a plunkr? – RKS Aug 15 '17 at 23:24