Laurence MacNeill
Laurence MacNeill

Reputation: 786

Angular 7, validating a dynamically-created group of check-boxes inside a FormArray

Ok, I'm sorry, but I just cannot figure out how to do this... There are literally hundreds of "how do I validate my form in Angular" posts on here, but none of them quite fit what I'm trying to do, and I just cannot figure out how to make it work. So I'm asking this here. Forgive me if it's been asked before.

I'm using a reactive form in Angular 7.2.2. I'm returning a list of items from the server, and I'm dynamically creating an array of checkboxes -- one for each item. I want the user to be able to check one or more of those check-boxes -- but they must check at least one of the checkboxes, or the form should be invalid, and the submit button should be disabled.

(FYI, there are other FormControls -- either <select> or <input type="text"> -- on my form that I have left out of this example. Those controls are not dynamically-created, so adding validators to them is trivial.)

Here's my component.html:

<form [formGroup]="myForm" (ngSubmit)="myFormSubmit()">
  <div style="width: 100%; height: 160px; padding: 3px; overflow: auto;">
    <label formArrayName="itemsToSelect" *ngFor="let item of myForm.controls.itemsToSelect.controls; let i = index" style="width: 85%; padding-left: 5px;">
      <input type="checkbox" formControlName="{{i}}">
        {{myListOfItems[i].item_id}}<br>
    </label>
  </div>
  <input type="submit" [disable]="!this.myForm.valid">
</form>

And here's my component.ts

myListOfItems: any = [];
myForm = new FormGroup({
  itemsToSelect: new FormArray([])
});

ngOnInit() {
  this.myService.getMyItems()
    .subscribe( (eventData) => {
      myListOfItems = eventData;
      myListOfItems.forEach((o, i) => {
        const control = new FormControl(false); //false so the checkbox defaults to not-selected
        (this.myForm.controls.itemsToSelect as FormArray).push(control);
      });
  });
}

myFormSubmit() {
  //Do stuff here that assumes at least one checkbox is checked.
}

So... How do I get it so myForm.valid is false when all the checkboxes are blank, but true when one or more checkbox is checked? Any help would be appreciated. Thanks.

Upvotes: 2

Views: 1192

Answers (2)

Laurence MacNeill
Laurence MacNeill

Reputation: 786

Ok, never mind. I finally figured it out...

When I create the Form Array I need to add a custom validator function:

myForm = new FormGroup({
  itemsToSelect: new FormArray([], [myCustomValidator])
});

And here's what that function looks like in my component.ts file:

myCustomValidator(control: FormArray) {
    let returnValue = false;
    for (let i=0; i<control.value.length; i++) {
        if (control.value[i]) {
            returnValue = true; // If any of the controls is checked, returnValue will be set to true at some point in this loop
        }
    }
    if (!returnValue) {
        return { controlStatus: {status: 'invalid'} }; // Returning an object of some sort, doesn't matter what, is how you indicate the control is not valid.
    } else {
        return null; // Returning null is how you indicate the control is valid.
    }
}

Upvotes: 2

Dmitriy Kavraiskyi
Dmitriy Kavraiskyi

Reputation: 319

You have to create separate validator for whole form. For example, you can have such validation for checkbox selection:

  function checkboxAtLeastOneSelected(form: FormGroup) {
     return Object.keys(form.controls)
             .some(control => form.controls[control].value === true) ? null : { invalidCheckboxes: true };
  }

After that, you have to place this validation function into second argument of  new FormGroup:

 myForm = new FormGroup({
  ....
}, checkboxAtLeastOneSelected);

Upvotes: 2

Related Questions