Angular ReactiveForms:是否产生一组复选框值?


104

给定绑定到同一复选框的列表formControlName,如何生成绑定到的复选框值的数组formControl,而不是简单地true/ false呢?

例:

<form [formGroup]="checkboxGroup">
    <input type="checkbox" id="checkbox-1" value="value-1" formControlName="myValues" />
    <input type="checkbox" id="checkbox-2" value="value-2" formControlName="myValues" />
    <input type="checkbox" id="checkbox-3" value="value-2" formControlName="myValues" />
</form>

checkboxGroup.controls['myValues'].value 目前生产:

true or false

我想要它产生什么:

['value-1', 'value-2', ...]

您找到解决方案了吗?
CP

这可能是设计表单中复选框最复杂的方法。这一点都不简单。
mwilson

8
有角度的。我要做的就是让mat-radio-group以我的反应形式绑定。我不记得在角度上为此付出太多努力。所有文章都指向同一件事。只是无法使其正常工作。其他一切都非常简单。我可能已经看了太久了。对于表单中的数组值,仍然感觉Wayyyyy过于复杂。
mwilson

3
是的,当我在2016年问这个问题时,这太糟糕了,而在2019年,它仍然很糟糕。–
ReactingToAngularVues

3
我没有在这个问题上增加很多,但我想让其他人知道我也有相同的看法。仅此一项就成为学习角电抗形式中最困难的部分。我觉得一点也不难。不过,我很高兴看到我并不孤单。因此,感谢您发布问题。
NorthStarCode

Answers:


52

在silentsod答案的帮助下,我编写了一个解决方案,以获取值而不是formBuilder中的状态。

我使用一种方法来添加或删除formArray中的值。可能是个不好的办法,但是行得通!

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++;
    });
  }
}

例如,当我提交表单时,我的模型如下所示:

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

仅缺少一件事,如果模型已具有检查值,则该函数将填充formArray。


使用示例输入数据库后,如何在加载数据时检查复选框是否已选中?
Devora

在此解决方案中,即使未选中复选框,表单也始终有效
Teja

myChoices: new FormArray([], Validators.required)
比克拉姆·纳特

48

这是使用https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html的好地方FormArray

首先,我们将使用FormBuilder或更新一个来构建控件数组FormArray

表格生成器

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

新的FormArray

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

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

这样做很容易,但是接下来我们将更改模板,并让模板引擎处理如何绑定到控件:

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>

这里我们循环访问我们的一套FormControls在我们myValues FormArray和每个控制我们结合[formControl]到控制,而不是在FormArray控制和<div>{{checkboxGroup.controls['myValues'].value}}</div>生产的true,false,true同时,也使您的模板语法有点不太手册。

您可以使用以下示例:http : //plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p= preview可以四处浏览


1
可能您应该删除id =“ xxx”,所以id应该是唯一的吧?
PeiSong Xiong

1
可以使用id的索引 *ngFor="let control of checkboxGroup.controls['myValues'].controls ; let i=index""
Mirza

9
这很酷,但是会产生一个完全通用的复选框数组。大概您将要加载数组或其他内容,并将每个复选框与其他值相关联。例如,如何为每个表单控件添加用于表单标签的文本字符串?
Askdesigners

NM我刚刚将其映射到外部数组旁边:p
Askdesigners

@Askdesigners您可以发布解决方案以包含复选框和标签吗?
eddygeek

25

即使从API异步填充了复选框信息,在Angular 6中执行此操作也比以前的版本中要容易得多。

首先要意识到的是,由于有了Angular 6的keyvalue管道,我们不再需要使用它FormArray,而可以嵌套一个FormGroup

首先,将FormBuilder传递给构造函数

constructor(
    private _formBuilder: FormBuilder,
) { }

然后初始化我们的表格。

ngOnInit() {

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

}

当我们的复选框选项数据可用时,对其进行迭代,然后可以将其FormGroup作为named 直接推入嵌套FormControl,而不必依赖于数字索引的查找数组。

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

最后,在模板中,我们只需要迭代keyvalue复选框的:无其他let index = i,复选框将自动按字母顺序排列:更加整洁。

<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>

1
在复选框值的简单硬编码数组的情况下也非常有用。然后,您可以立即在ngOnInit()中使用类似的for循环添加表单控件,并且表单中的复选框将动态反映复选框值数组
Arjan

3
这仍然摘录[key1 = true,key2 = false,key3 = true]。我们想要['key1','key3']
f.khantsis

@ f.khantsis可以这样操作:`const value = {key1:true,key2:false,key3:true}; const list = Object.entries(value).filter(([[_,isSelected])=> isSelected).map(([key])=>键); console.log(list); `
zauni

1
最好的解决方案恕我直言。您可以将分配的任务const checkboxes = ..放在外部;)
Bernoulli IT

当项目键与表单上的另一个字段相同时,会发生什么?例如,我有两个不同的复选框数组,每个数组都有键“ Small”,“ Medium”和“ Large”?
纽克里克

9

如果您正在寻找JSON格式的复选框值

{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }

完整的例子在这里

对于将“国家名称”用作复选框值而不是问题中的复选框值,我深表歉意。进一步说明-

为表单创建一个FormGroup

 createForm() {

    //Form Group for a Hero Form
    this.heroForm = this.fb.group({
      name: '',
      countries: this.fb.array([])
    });

    let countries=['US','Germany','France'];

    this.setCountries(countries);}
 }

让每个复选框都是一个由对象构造的FormGroup,该对象的唯一属性是复选框的值。

 setCountries(countries:string[]) {

    //One Form Group for one country
    const countriesFGs = countries.map(country =>{
            let obj={};obj[country]=true;
            return this.fb.group(obj)
    });

    const countryFormArray = this.fb.array(countriesFGs);
    this.heroForm.setControl('countries', countryFormArray);
  }

复选框的FormGroups数组用于设置父Form中“国家”的控件。

  get countries(): FormArray {
      return this.heroForm.get('countries') as FormArray;
  };

在模板中,使用管道获取复选框控件的名称

  <div formArrayName="countries" class="well well-lg">
      <div *ngFor="let country of countries.controls; let i=index" [formGroupName]="i" >
          <div *ngFor="let key of country.controls | mapToKeys" >
              <input type="checkbox" formControlName="{{key.key}}">{{key.key}}
          </div>
      </div>
  </div>

6

TL; DR

  1. 我更喜欢使用FormGroup填充复选框列表
  2. 编写自定义验证器以选中至少一个复选框
  3. 工作示例https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected

这有时也让我感到震惊,因此我确实尝试了FormArray和FormGroup方法。

大多数情况下,复选框列表填充在服务器上,我是通过API收到的。但是有时您会看到一组带有预定义值的静态复选框。对于每个用例,将使用相应的FormArray或FormGroup。

基本上FormArray是的变体FormGroup。关键区别在于其数据被序列化为一个数组(与之相对的是在FormGroup中被序列化为一个对象)。当您不知道组中将存在多少控件(例如动态表单)时,这可能特别有用。

为了简单起见,假设您有一个简单的create product表单,

  • 一个必需的产品名称文本框。
  • 要选择的类别列表,要求至少要检查一个类别。假定将从服务器检索列表。

首先,我建立了一个仅具有产品名称formControl的表单。这是必填字段。

this.form = this.formBuilder.group({
    name: ["", Validators.required]
});

由于类别是动态呈现的,因此在数据准备好以后,我将不得不将这些数据添加到表单中。

this.getCategories().subscribe(categories => {
    this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
    this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})

有两种方法来建立类别列表。

1.表格数组

  buildCategoryFormArr(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormArray {
    const controlArr = categories.map(category => {
      let isSelected = selectedCategoryIds.some(id => id === category.id);
      return this.formBuilder.control(isSelected);
    })
    return this.formBuilder.array(controlArr, atLeastOneCheckboxCheckedValidator())
  }
<div *ngFor="let control of categoriesFormArr?.controls; let i = index" class="checkbox">
  <label><input type="checkbox" [formControl]="control" />
    {{ categories[i]?.title }}
  </label>
</div>

buildCategoryFormGroup将返回一个FormArray。它还将所选值的列表作为参数,因此,如果您想将表单重用于编辑数据,这可能会有所帮助。出于创建新产品表格的目的,它尚不适用。

注意,当您尝试访问formArray值时。它看起来像[false, true, true]。要获得选定ID的列表,需要更多的工作来从列表中进行检查,但要基于数组索引。对我来说听起来不太好,但是可以。

get categoriesFormArraySelectedIds(): string[] {
  return this.categories
  .filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
  .map(cat => cat.id);
}

这就是为什么我开始FormGroup为此使用

2.表格组

formGroup的不同之处在于它将表单数据存储为对象,这需要一个键和一个表单控件。因此,最好将密钥设置为categoryId,然后稍后再检索。

buildCategoryFormGroup(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormGroup {
  let group = this.formBuilder.group({}, {
    validators: atLeastOneCheckboxCheckedValidator()
  });
  categories.forEach(category => {
    let isSelected = selectedCategoryIds.some(id => id === category.id);
    group.addControl(category.id, this.formBuilder.control(isSelected));
  })
  return group;
}
<div *ngFor="let item of categories; let i = index" class="checkbox">
  <label><input type="checkbox" [formControl]="categoriesFormGroup?.controls[item.id]" /> {{ categories[i]?.title }}
  </label>
</div>

表单组的值如下所示:

{
    "category1": false,
    "category2": true,
    "category3": true,
}

但大多数情况下,我们只想获取categoryIds的列表["category2", "category3"]。我还必须写一个获取这些数据的方法。与formArray相比,我更喜欢这种方法,因为我实际上可以从表单本身获取值。

  get categoriesFormGroupSelectedIds(): string[] {
    let ids: string[] = [];
    for (var key in this.categoriesFormGroup.controls) {
      if (this.categoriesFormGroup.controls[key].value) {
        ids.push(key);
      }
      else {
        ids = ids.filter(id => id !== key);
      }
    }
    return ids;
  }

3.选择至少要检查一个复选框的自定义验证器

我使验证程序检查至少选中了X复选框,默认情况下它将仅针对一个复选框进行检查。

export function atLeastOneCheckboxCheckedValidator(minRequired = 1): ValidatorFn {
  return function validate(formGroup: FormGroup) {
    let checked = 0;

    Object.keys(formGroup.controls).forEach(key => {
      const control = formGroup.controls[key];

      if (control.value === true) {
        checked++;
      }
    });

    if (checked < minRequired) {
      return {
        requireCheckboxToBeChecked: true,
      };
    }

    return null;
  };
}

6

我在这里看不到任何解决方案可以最大程度地使用反应形式完全回答问题,因此这是我的解决方案。


摘要

这是详细解释的精髓所在,还有StackBlitz示例。

  1. 使用FormArray该复选框并初始化形式。
  2. valueChanges当您希望表单显示某些内容但将其他内容存储在组件中时,observable非常适合。在此处将true/ false值映射到所需的值。
  3. false提交时过滤掉值。
  4. 取消valueChanges可观察的订阅。

StackBlitz示例


详细说明

使用FormArray定义表单

如答案中已提到的标记为正确。FormArray在您希望将数据存储在数组中的情况下,这种方法是可行的。因此,您需要做的第一件事就是创建表单。

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))
});

这只会将所有复选框的初始值设置为false

接下来,我们需要在模板中注册这些表单变量,然后遍历checkboxes数组(不是FormArray复选框数据)以在模板中显示它们。

<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>

充分利用valueChanges

这是我在此处给出的任何答案中未提及的部分。在这种情况下,我们希望显示所述数据但将其存储为其他数据,因此valueChanges可观察性非常有用。使用valueChanges,我们可以观察到在变化checkboxes,然后maptrue/ false从接收到的值FormArray,以所期望的数据。请注意,这不会更改复选框的选择,因为传递给复选框的任何真实值会将其标记为已选中,反之亦然。

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 }
    );
});

这基本上将FormArray值映射到原始checkboxes数组,并value在复选框标记为的情况下true返回,否则返回false。该emitEvent: false是因为设置在这里的重要FormArray,没有它会导致价值 valueChanges发出一个事件产生一个死循环。通过设置emitEventfalse,我们确保在valueChanges此处设置该值时不会发出observable。

过滤掉错误的值

我们无法直接过滤中的false值,FormArray因为这样做会混淆模板,因为它们已绑定到复选框。因此,最好的解决方案是false在提交过程中过滤掉值。使用价差运算符执行此操作。

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
}

这基本上从中筛选出虚假checkboxes

取消订阅valueChanges

最后,别忘了退订 valueChanges

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

注意:在特殊情况下,不能将值设置为FormArrayin valueChanges,即,如果复选框值设置为number 0。这将使它看起来像无法选中该复选框,因为选中该复选框会将设置FormControl为数字0(伪造的值),从而使其保持未选中状态。最好不要将数字0用作值,但是如果需要,则必须有条件地将其设置0为某些真实值,例如字符串'0'或只是简单的值,true然后在提交时将其转换回数字0

StackBlitz示例

当您希望将默认值传递给复选框时,StackBlitz也具有代码,以便将默认值标记为UI。


4

单击一个事件,然后将true的值手动更改为复选框代表的名称,则名称或true的值将相同,您可以获取所有值,而不是true / false列表。例如:

component.html

<form [formGroup]="customForm" (ngSubmit)="onSubmit()">
    <div class="form-group" *ngFor="let parameter of parameters"> <!--I iterate here to list all my checkboxes -->
        <label class="control-label" for="{{parameter.Title}}"> {{parameter.Title}} </label>
            <div class="checkbox">
              <input
                  type="checkbox"
                  id="{{parameter.Title}}"
                  formControlName="{{parameter.Title}}"
                  (change)="onCheckboxChange($event)"
                  > <!-- ^^THIS^^ is the important part -->
             </div>
      </div>
 </form>

component.ts

onCheckboxChange(event) {
    //We want to get back what the name of the checkbox represents, so I'm intercepting the event and
    //manually changing the value from true to the name of what is being checked.

    //check if the value is true first, if it is then change it to the name of the value
    //this way when it's set to false it will skip over this and make it false, thus unchecking
    //the box
    if(this.customForm.get(event.target.id).value) {
        this.customForm.patchValue({[event.target.id] : event.target.id}); //make sure to have the square brackets
    }
}

当Angular Forms将其更改为true或false时,将捕获该事件,如果为true,则将名称更改为复选框代表的名称,如果需要,还将在检查true / false时将其评估为true好。


这使我走上了正确的道路,我最终完成了this.customForm.patchValue({[event.target.id]:event.target.checked});
Demodave

4

如果要使用Angular反应形式(https://angular.io/guide/reactive-forms)。

您可以使用一个窗体控件来管理复选框组的输出值。

零件

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

管理清单输入的模型/状态。该模型允许您将当前状态映射到所需的任何值格式。

模型:

{
   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 表格控制

此控件存储要保存为例如的值

值输出: "value_1,value_2"

观看演示https://stackblitz.com/edit/angular-multi-checklist


轻松地为我最好的解决方案。非常感谢。
6

2

我的解决方案-使用材质视图为Angular 5解决了
该问题连接是通过

formArrayName =“ notification”

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

这样,它可以以一种形式用于多个复选框数组。只需设置每次要连接的控件数组的名称即可。

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>

最后,您将保存带有原始记录ID数组的表单以进行保存/更新。 UI视图

表单json的相关部分

如有任何改进的建议,我们将不胜感激。


0

模板部分:-

    <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>

控制器部分:-

        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));
                }
             }

        }

1
嗨@EchoLogic ..如有任何查询,请让我知道
Abhishek Srivastava

1
这不是使用ReactiveForms,而是常规表单,因此无法回答问题
Guillaume19年

0

加我的5美分)我的问题模型

{
   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)
   }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.