0

I would like to implement validations to my checkbox FormBuilder.array but despite checking one of the boxes, the form will still be invalid.

I've tried Validators.required and Validators.requiredTrue but it still doesn't work as expected

TS:

this.Form = this.fb.group({
  stage: this.fb.array([], Validators.required)
})

HTML:

<div class="form-check-label">
  <label class="checkbox-inline">
    <input type="checkbox" class="checkbox" name="none" value="1" #noneChk
      (change)="onCheckArray($event, Form.value.stage)">
    Stage 1
  </label>
</div>
<div class="form-check-label">
  <label class="checkbox-inline">
    <input type="checkbox" class="checkbox" name="self" value="2" #selfChk
      (change)="onCheckArray($event, Form.value.stage)">
    Stage 2
  </label>
</div>

<p>{{ this.Form.valid | json }}</p>

I have my StackBlitz here

mhfour
  • 255
  • 2
  • 8
  • 19
  • Mhfour, Validators.required not work in a formArray because the value of a FormArray is always "something" -an array-. You need make a custom validator that check if one element of the formArray is checked – Eliseo Jun 20 '19 at 07:23
  • to understand a checkbox, check this question of stackblitz. https://stackoverflow.com/questions/56619632/angular-formarray-checkboxes/56622210#56622210. NOTE: If you want use the custom form control, but not want use material, just replace in check-boxlist-component `{{key?_data[i][text]:_data[i]}}` by `` – Eliseo Jun 20 '19 at 07:37
  • is there an example for the custom validations? i tried a few but it didnt work – mhfour Jun 20 '19 at 07:45
  • I add an answer to make a custom validator. – Eliseo Jun 20 '19 at 09:51

2 Answers2

1

About your stackblitz, I forked your stackblitz

The problem is that your function onCheckArray don't change the FormArray (yes, you changed the value, but not the FormArray, so there was any validation. See your function edited

//I repite the getter stage, so the answer can be understood

get stage(): FormArray {
  return this.Form.get('stage') as FormArray;
}

onCheckArray(event) { //you needn't send the value
    /* Selected */
    if (event.target.checked) {
      // Add a new control in the arrayForm, 
      // use push, but add a new FormControl TO the formArray
      this.stage.push(new FormControl(event.target.value));
    } else {
      /* unselected */
      // find the unselected element
      let i: number = 0;

       //we iterate over the formArray
      for (i = 0; i < this.stage.value.length; i++) {
        if (this.stage.value[i] == event.target.value) {
          //use removeAt(i)
          this.stage.removeAt(i);
          return;
        }
      }
    }
  }

Well, your function validators can be more easy, just check the length of the array

minSelectedCheckboxes(min = 1) {
    return (formArray: FormArray) => {
      return formArray.controls.length >= min ? null : { required: true };
    };

}

And there are a problem if you initialize the FormArray

this.Form = this.fb.group({
  stage: this.fb.array([new FormControl("3")], this.minSelectedCheckboxes())
})

Your .html must be like

 <div class="form-check-label">
    <label class="checkbox-inline">
      <input type="checkbox" class="checkbox" name="none" value="1" #noneChk 
          <!--see how indicate when is checked-->
          [checked]="stage.value.indexOf('1')>=0"
           (change)="onCheckArray($event)">
      1
    </label>
  </div>

Well, I you are asking about a more simple way to manage an array of checkboxes, we can take another aproach. Our FormArray will be an array of controls with value true/false, and we has a function taht transform this array in our values.

  options=["1","2","3","4"]; //our options
  get valuesSelected() ; //a function that return the options selected
  {
      return this.options.filter((x,index)=>this.newForm.get('stage').value[index])
  }
  //see how create the formArray, always has the same number of 
  //elements that our options

  this.newForm=new FormGroup({
        stage:new FormArray(this.options
             .map(x=>new FormControl(false)),this.minTrueCheckboxes())
  })

  minTrueCheckboxes(min = 1) {
    return (formArray: FormArray) => {
      return formArray.value.filter(x=>x).length>=min? null : { required: true };
    };
  }

And our .html becomes like

<form class="form" [formGroup]="newForm" (ngSubmit)="onSubmit()">
  <div formArrayName="stage">
    <label class="checkbox-inline" *ngFor="let control of newForm.get('stage').controls;let i=index">
      <input type="checkbox" class="checkbox" [formControl]="control" >
          {{options[i]}}
    </label>
  </div>
</form>

<p>{{ valuesSelected | json }}</p>
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • thanks so much it works now! just wondering, if i want the checkboxes to be empty by default, how do i do that? i removed the value 3 from the formControl in fb.array but the initial value will be null, how do i make it empty? – mhfour Jun 21 '19 at 08:01
  • `stage: this.fb.array([], this.minSelectedCheckboxes())`, just an empty array – Eliseo Jun 21 '19 at 08:06
0

A custom validator can be make in the own component or outside.

You can see the two models in this stackblitz

//In the own component
this.form = new FormGroup({
      checks: new FormArray([
        new FormControl(true),
        new FormControl(false),
      ], this.AtLeatOne()), //sii that call it as this.AtLeastOne
    });

AtLeatOne() {
    return (control: FormArray) => {
      if (control.value.find(x => x))
        return null
      return { "error": "You must select at least one option" }
    }
  }

//OutSide
    this.form2 = new FormGroup({
      checks: new FormArray([
        new FormControl(true),
        new FormControl(false),
      ], AtLeatOneValidator())
    });

export function AtLeatOneValidator()
{
  return (control: FormArray)=> {
      if (control.value.find(x => x))
        return null
      return { "error": "You must select at least one option" }
  }
}

Well, I put the most general case for if we want to send an argument to the function (Imagine we want that the user select al least 2 options.)

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • hi i tried this but could not work. for my case, i use the Form.value.whateverControlName and use that as the array. is there a custom validator that can be applied to that? – mhfour Jun 20 '19 at 10:54
  • @mhfour, I can't see the form in your .html. take a look to the stackblitz of my answer if you want to see an example, and update your question with the .html – Eliseo Jun 20 '19 at 12:52
  • hi, i just added my stackblitz to my question. would greatly appreciate the help – mhfour Jun 21 '19 at 02:20