Angular ReactiveForms:生成一个复选框值数组?

Rea*_*ues 80 javascript checkbox angular2-forms angular

鉴于绑定到同一个复选框列表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>
Run Code Online (Sandbox Code Playgroud)

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

true or false
Run Code Online (Sandbox Code Playgroud)

我想要它产生什么:

['value-1', 'value-2', ...]
Run Code Online (Sandbox Code Playgroud)

sil*_*sod 41

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

首先,我们将使用FormBuilder或新增a来构建我们的控件数组FormArray

FormBuilder

this.checkboxGroup = _fb.group({
  myValues: _fb.array([true, false, true])
});
Run Code Online (Sandbox Code Playgroud)

新的FormArray

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

this.checkboxGroup = _fb.group({
  myValues: checkboxArray
});
Run Code Online (Sandbox Code Playgroud)

很容易做到,但随后我们将改变我们的模板并让模板引擎处理我们绑定到控件的方式:

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>
Run Code Online (Sandbox Code Playgroud)

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

您可以使用此示例:http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p =preview to to around around

  • 这很酷,但会生成一个完全通用的复选框.大概你会加载一个数组或其他东西,并将每个复选框与其他值相关联.例如,如何在表单标签中添加用于每个表单控件的文本字符串? (7认同)

Guy*_*age 35

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

我使用一种方法在formArray中添加或删除值.它可能是一个糟糕的approch,但它的工作原理!

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>
Run Code Online (Sandbox Code Playgroud)

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++;
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

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

  otherControls : "foo",
  myChoices : ['value1', 'value2']
Run Code Online (Sandbox Code Playgroud)

只缺少一件事,如果您的模型已经检查了值,则填充formArray的函数.

  • “但是它有效!” 这就是科技债务的开始。这不是反应式的做法。在每个复选框输入上使用表单控件的好处之一是,即使您将它们重新添加到 DOM,它们也可以记住它们的状态。 (8认同)

nas*_*h11 13

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


概括

这是详细解释的要点以及 StackBlitz 示例。

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

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))
});
Run Code Online (Sandbox Code Playgroud)

这只会将所有复选框的初始值设置为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>
Run Code Online (Sandbox Code Playgroud)

使用 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 }
    );
});
Run Code Online (Sandbox Code Playgroud)

这基本上将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
}
Run Code Online (Sandbox Code Playgroud)

这基本上过滤掉falsy从值checkboxes

取消订阅 valueChanges

最后别忘了退订 valueChanges

ngOnDestroy() {
    this.subscription.unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

注意:有一种特殊情况,值不能设置为FormArrayin valueChanges,即如果复选框值设置为 number 0。这将使该复选框看起来无法选中,因为选择该复选框会将 设置FormControl为数字0(假值),因此保持未选中状态。最好不要将数字0用作值,但如果需要,您必须有条件地设置0为某个真实值,例如字符串'0'或只是普通值,true然后在提交时将其转换回数字0

StackBlitz 示例

StackBlitz 还提供了代码,用于何时将默认值传递给复选框,以便在 UI 中将它们标记为已选中。

  • 复杂值不起作用,因为复选框的值必须为 true 或 false。所以这个解决方案看上去还是最好的。 (2认同)

小智 11

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

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

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

constructor(
    private _formBuilder: FormBuilder,
) { }
Run Code Online (Sandbox Code Playgroud)

然后初始化我们的表格。

ngOnInit() {

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

}
Run Code Online (Sandbox Code Playgroud)

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

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

最后,在模板中,我们只需要迭代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>
Run Code Online (Sandbox Code Playgroud)

  • 恕我直言,最好的解决方案。您可以将 `const checkboxes = ..` 的赋值放在 foreach 之外;) (3认同)
  • 这仍然摘录[key1 = true,key2 = false,key3 = true]。我们想要['key1','key3'] (2认同)
  • 这个解决方案看起来很好很干净,但我遇到了一个错误:类型“未知”不能分配给类型“数字”。&lt;input type="checkbox" [formControlName]="item.key" [value]="item.value" /&gt; {{ item.key }} (2认同)

tru*_*k18 9

TL; 博士

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

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

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

基本上FormArrayFormGroup. 关键的区别在于它的数据被序列化为一个数组(而不是在 FormGroup 的情况下被序列化为一个对象)。当您不知道组中将出现多少控件(如动态表单)时,这可能特别有用。

为简单起见,假设您有一个简单的创建产品表单

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

首先,我设置了一个只有产品名称 formControl 的表单。它是必填字段。

this.form = this.formBuilder.group({
    name: ["", Validators.required]
});
Run Code Online (Sandbox Code Playgroud)

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

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

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

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())
  }
Run Code Online (Sandbox Code Playgroud)
this.form = this.formBuilder.group({
    name: ["", Validators.required]
});
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么我想出使用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;
}
Run Code Online (Sandbox Code Playgroud)
this.getCategories().subscribe(categories => {
    this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
    this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Run Code Online (Sandbox Code Playgroud)

表单组的值将如下所示:

{
    "category1": false,
    "category2": true,
    "category3": true,
}
Run Code Online (Sandbox Code Playgroud)

但大多数情况下,我们只想将 categoryIds 列表作为["category2", "category3"]. 我还必须写一个 get 来获取这些数据。与 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;
  }
Run Code Online (Sandbox Code Playgroud)

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;
  };
}
Run Code Online (Sandbox Code Playgroud)


aCi*_*CiD 8

如果您要查找JSON格式的复选框值

{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }
Run Code Online (Sandbox Code Playgroud)

完整的例子.

我为使用国家/地区名称作为复选框值而不是问题中的那些而道歉.进一步说明 -

为表单创建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);}
 }
Run Code Online (Sandbox Code Playgroud)

让每个复选框都是一个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);
  }
Run Code Online (Sandbox Code Playgroud)

复选框的FormGroup数组用于设置父Form中'countries'的控件.

  get countries(): FormArray {
      return this.heroForm.get('countries') as FormArray;
  };
Run Code Online (Sandbox Code Playgroud)

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

  <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>
Run Code Online (Sandbox Code Playgroud)


Rob*_*rib 7

如果您想使用 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(',');
  }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

checklist表单控制

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

值输出:"value_1,value_2"

请参阅https://stackblitz.com/edit/angular-multi-checklist上的演示