Angular ReactiveForms: Producing an array of checkbox values?

With the help of silentsod answer, I wrote a solution to get values instead of states in my formBuilder.

I use a method to add or remove values in the formArray. It may be a bad approch, but it works !

component.html

<div *ngFor="let choice of checks; let i=index" class="col-md-2">
  <label>
    <input type="checkbox" [value]="choice.value" (change)="onCheckChange($event)">
    {{choice.description}}
  </label>
</div>

component.ts

// For example, an array of choices
public checks: Array<ChoiceClass> = [
  {description: 'descr1', value: 'value1'},
  {description: "descr2", value: 'value2'},
  {description: "descr3", value: 'value3'}
];

initModelForm(): FormGroup{
  return this._fb.group({
    otherControls: [''],
    // The formArray, empty 
    myChoices: new FormArray([]),
  }
}

onCheckChange(event) {
  const formArray: FormArray = this.myForm.get('myChoices') as FormArray;

  /* Selected */
  if(event.target.checked){
    // Add a new control in the arrayForm
    formArray.push(new FormControl(event.target.value));
  }
  /* unselected */
  else{
    // find the unselected element
    let i: number = 0;

    formArray.controls.forEach((ctrl: FormControl) => {
      if(ctrl.value == event.target.value) {
        // Remove the unselected element from the arrayForm
        formArray.removeAt(i);
        return;
      }

      i++;
    });
  }
}

When I submit my form, for example my model looks like:

  otherControls : "foo",
  myChoices : ['value1', 'value2']

Only one thing is missing, a function to fill the formArray if your model already has checked values.


It's significantly easier to do this in Angular 6 than it was in previous versions, even when the checkbox information is populated asynchronously from an API.

The first thing to realise is that thanks to Angular 6's keyvalue pipe we don't need to have to use FormArray anymore, and can instead nest a FormGroup.

First, pass FormBuilder into the constructor

constructor(
    private _formBuilder: FormBuilder,
) { }

Then initialise our form.

ngOnInit() {

    this.form = this._formBuilder.group({
        'checkboxes': this._formBuilder.group({}),
    });

}

When our checkbox options data is available, iterate it and we can push it directly into the nested FormGroup as a named FormControl, without having to rely on number indexed lookup arrays.

const checkboxes = <FormGroup>this.form.get('checkboxes');
options.forEach((option: any) => {
    checkboxes.addControl(option.title, new FormControl(true));
});

Finally, in the template we just need to iterate the keyvalue of the checkboxes: no additional let index = i, and the checkboxes will automatically be in alphabetical order: much cleaner.

<form [formGroup]="form">

    <h3>Options</h3>

    <div formGroupName="checkboxes">

        <ul>
            <li *ngFor="let item of form.get('checkboxes').value | keyvalue">
                <label>
                    <input type="checkbox" [formControlName]="item.key" [value]="item.value" /> {{ item.key }}
                </label>
            </li>
        </ul>

    </div>

</form>

Here's a good place to use the FormArray https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html

To start we'll build up our array of controls either with a FormBuilder or newing up a FormArray

FormBuilder

this.checkboxGroup = _fb.group({
  myValues: _fb.array([true, false, true])
});

new FormArray

let checkboxArray = new FormArray([
  new FormControl(true),
  new FormControl(false),
  new FormControl(true)]);

this.checkboxGroup = _fb.group({
  myValues: checkboxArray
});

Easy enough to do, but then we're going to change our template and let the templating engine handle how we bind to our controls:

template.html

<form [formGroup]="checkboxGroup">
    <input *ngFor="let control of checkboxGroup.controls['myValues'].controls"
    type="checkbox" id="checkbox-1" value="value-1" [formControl]="control" />     
  </form>

Here we're iterating over our set of FormControls in our myValues FormArray and for each control we're binding [formControl] to that control instead of to the FormArray control and <div>{{checkboxGroup.controls['myValues'].value}}</div> produces true,false,true while also making your template syntax a little less manual.

You can use this example: http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview to poke around