Angular ReactiveForms: Producing an array of checkbox values?

前端 未结 12 602
执念已碎
执念已碎 2020-11-29 17:16

Given a list of checkboxes bound to the same formControlName, how can I produce an array of checkbox values bound to the formControl, rather than s

相关标签:
12条回答
  • 2020-11-29 18:04

    I don't see a solution here that completely answers the question using reactive forms to its fullest extent so here's my solution for the same.


    Summary

    Here's the pith of the detailed explanation along with a StackBlitz example.

    1. Use FormArray for the checkboxes and initialize the form.
    2. The valueChanges observable is perfect for when you want the form to display something but store something else in the component. Map the true/false values to the desired values here.
    3. Filter out the false values at the time of submission.
    4. Unsubscribe from valueChanges observable.

    StackBlitz example


    Detailed explanation

    Use FormArray to define the form

    As already mentioned in the answer marked as correct. FormArray is the way to go in such cases where you would prefer to get the data in an array. So the first thing you need to do is create the form.

    checkboxGroup: FormGroup;
    checkboxes = [{
        name: 'Value 1',
        value: 'value-1'
    }, {
        name: 'Value 2',
        value: 'value-2'
    }];
    
    this.checkboxGroup = this.fb.group({
        checkboxes: this.fb.array(this.checkboxes.map(x => false))
    });
    

    This will just set the initial value of all the checkboxes to false.

    Next, we need to register these form variables in the template and iterate over the checkboxes array (NOT the FormArray but the checkbox data) to display them in the template.

    <form [formGroup]="checkboxGroup">
        <ng-container *ngFor="let checkbox of checkboxes; let i = index" formArrayName="checkboxes">
            <input type="checkbox" [formControlName]="i" />{{checkbox.name}}
        </ng-container>
    </form>
    

    Make use of the valueChanges observable

    Here's the part I don't see mentioned in any answer given here. In situations such as this, where we would like to display said data but store it as something else, the valueChanges observable is very helpful. Using valueChanges, we can observe the changes in the checkboxes and then map the true/false values received from the FormArray to the desired data. Note that this will not change the selection of the checkboxes as any truthy value passed to the checkbox will mark it as checked and vice-versa.

    subscription: Subscription;
    
    const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
    this.subscription = checkboxControl.valueChanges.subscribe(checkbox => {
        checkboxControl.setValue(
            checkboxControl.value.map((value, i) => value ? this.checkboxes[i].value : false),
            { emitEvent: false }
        );
    });
    

    This basically maps the FormArray values to the original checkboxes array and returns the value in case the checkbox is marked as true, else it returns false. The emitEvent: false is important here since setting the FormArray value without it will cause valueChanges to emit an event creating an endless loop. By setting emitEvent to false, we are making sure the valueChanges observable does not emit when we set the value here.

    Filter out the false values

    We cannot directly filter the false values in the FormArray because doing so will mess up the template since they are bound to the checkboxes. So the best possible solution is to filter out the false values during submission. Use the spread operator to do this.

    submit() {
        const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
        const formValue = {
            ...this.checkboxGroup.value,
            checkboxes: checkboxControl.value.filter(value => !!value)
        }
        // Submit formValue here instead of this.checkboxGroup.value as it contains the filtered data
    }
    

    This basically filters out the falsy values from the checkboxes.

    Unsubscribe from valueChanges

    Lastly, don't forget to unsubscribe from valueChanges

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
    

    Note: There is a special case where a value cannot be set to the FormArray in valueChanges, i.e if the checkbox value is set to the number 0. This will make it look like the checkbox cannot be selected since selecting the checkbox will set the FormControl as the number 0 (a falsy value) and hence keep it unchecked. It would be preferred not to use the number 0 as a value but if it is required, you have to conditionally set 0 to some truthy value, say string '0' or just plain true and then on submitting, convert it back to the number 0.

    StackBlitz example

    The StackBlitz also has code for when you want to pass default values to the checkboxes so they get marked as checked in the UI.

    0 讨论(0)
  • 2020-11-29 18:04

    TEMPLATE PART:-

        <div class="form-group">
             <label for="options">Options:</label>
             <div *ngFor="let option of options">
                <label>
                    <input type="checkbox"
                       name="options"
                       value="{{option.value}}"
                       [(ngModel)]="option.checked"
                                    />
                      {{option.name}}
                      </label>
                  </div>
                  <br/>
             <button (click)="getselectedOptions()"  >Get Selected Items</button>
         </div>
    

    CONTROLLER PART:-

            export class Angular2NgFor {
    
              constructor() {
                 this.options = [
                  {name:'OptionA', value:'first_opt', checked:true},
                  {name:'OptionB', value:'second_opt', checked:false},
                  {name:'OptionC', value:'third_opt', checked:true}
                 ];
    
    
                 this.getselectedOptions = function() {
                   alert(this.options
                      .filter(opt => opt.checked)
                      .map(opt => opt.value));
                    }
                 }
    
            }
    
    0 讨论(0)
  • 2020-11-29 18:04

    Add my 5 cents) My question model

    {
       name: "what_is_it",
       options:[
         {
          label: 'Option name',
          value: '1'
         },
         {
          label: 'Option name 2',
          value: '2'
         }
       ]
    }
    

    template.html

    <div class="question"  formGroupName="{{ question.name }}">
    <div *ngFor="let opt of question.options; index as i" class="question__answer" >
      <input 
        type="checkbox" id="{{question.name}}_{{i}}"
        [name]="question.name" class="hidden question__input" 
        [value]="opt.value" 
        [formControlName]="opt.label"
       >
      <label for="{{question.name}}_{{i}}" class="question__label question__label_checkbox">
          {{opt.label}}
      </label>
    </div>
    

    component.ts

     onSubmit() {
        let formModel = {};
        for (let key in this.form.value) {
          if (typeof this.form.value[key] !== 'object') { 
            formModel[key] = this.form.value[key]
          } else { //if formgroup item
            formModel[key] = '';
            for (let k in this.form.value[key]) {
              if (this.form.value[key][k])
                formModel[key] = formModel[key] + k + ';'; //create string with ';' separators like 'a;b;c'
            }
          }
        }
         console.log(formModel)
       }
    
    0 讨论(0)
  • 2020-11-29 18:06

    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

    0 讨论(0)
  • 2020-11-29 18:06

    My solution - solved it for Angular 5 with Material View
    The connection is through the

    formArrayName="notification"

    (change)="updateChkbxArray(n.id, $event.checked, 'notification')"

    This way it can work for multiple checkboxes arrays in one form. Just set the name of the controls array to connect each time.

    constructor(
      private fb: FormBuilder,
      private http: Http,
      private codeTableService: CodeTablesService) {
    
      this.codeTableService.getnotifications().subscribe(response => {
          this.notifications = response;
        })
        ...
    }
    
    
    createForm() {
      this.form = this.fb.group({
        notification: this.fb.array([])...
      });
    }
    
    ngOnInit() {
      this.createForm();
    }
    
    updateChkbxArray(id, isChecked, key) {
      const chkArray = < FormArray > this.form.get(key);
      if (isChecked) {
        chkArray.push(new FormControl(id));
      } else {
        let idx = chkArray.controls.findIndex(x => x.value == id);
        chkArray.removeAt(idx);
      }
    }
    <div class="col-md-12">
      <section class="checkbox-section text-center" *ngIf="notifications  && notifications.length > 0">
        <label class="example-margin">Notifications to send:</label>
        <p *ngFor="let n of notifications; let i = index" formArrayName="notification">
          <mat-checkbox class="checkbox-margin" (change)="updateChkbxArray(n.id, $event.checked, 'notification')" value="n.id">{{n.description}}</mat-checkbox>
        </p>
      </section>
    </div>

    At the end you are getting to save the form with array of original records id's to save/update.

    Will be happy to have any remarks for improvement.

    0 讨论(0)
  • 2020-11-29 18:16

    If you want to use an Angular reactive form (https://angular.io/guide/reactive-forms).

    You can use one form control to manage the outputted value of the group of checkboxes.

    component

    import { Component } from '@angular/core';
    import { FormGroup, FormControl } from '@angular/forms';
    import { flow } from 'lodash';
    import { flatMap, filter } from 'lodash/fp';
    
    @Component({
      selector: 'multi-checkbox',
      templateUrl: './multi-checkbox.layout.html',
    })
    export class MultiChecboxComponent  {
    
      checklistState = [ 
          {
            label: 'Frodo Baggins',
            value: 'frodo_baggins',
            checked: false
          },
          {
            label: 'Samwise Gamgee',
            value: 'samwise_gamgee',
            checked: true,
          },
          {
            label: 'Merry Brandybuck',
            value: 'merry_brandybuck',
            checked: false
          }
        ];
    
      form = new FormGroup({
        checklist : new FormControl(this.flattenValues(this.checklistState)),
      });
    
    
      checklist = this.form.get('checklist');
    
      onChecklistChange(checked, checkbox) {
        checkbox.checked = checked;
        this.checklist.setValue(this.flattenValues(this.checklistState));
      }
    
      flattenValues(checkboxes) {
        const flattenedValues = flow([
          filter(checkbox => checkbox.checked),
          flatMap(checkbox => checkbox.value )
        ])(checkboxes)
        return flattenedValues.join(',');
      }
    }
    

    html

    <form [formGroup]="form">
        <label *ngFor="let checkbox of checklistState" class="checkbox-control">
        <input type="checkbox" (change)="onChecklistChange($event.target.checked, checkbox)" [checked]="checkbox.checked" [value]="checkbox.value" /> {{ checkbox.label }}
      </label>
    </form>
    

    checklistState

    Manages the model/state of the checklist inputs. This model allows you to map the current state to whatever value format you need.

    Model:

    {
       label: 'Value 1',
       value: 'value_1',
       checked: false
    },
    {
      label: 'Samwise Gamgee',
      value: 'samwise_gamgee',
      checked: true,
    },
    {
      label: 'Merry Brandybuck',
      value: 'merry_brandybuck',
      checked: false
    }
    

    checklist Form Control

    This control stores the value would like to save as e.g

    value output: "value_1,value_2"

    See demo at https://stackblitz.com/edit/angular-multi-checklist

    0 讨论(0)
提交回复
热议问题