Angular ReactiveForms:生成一组复选框值?
Angular ReactiveForms: Producing an array of checkbox values?
给定绑定到相同 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', ...]
模板部分:-
<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));
}
}
}
这里是使用FormArray
https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
的好地方
首先,我们将使用 FormBuilder
或新建 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
});
做起来很容易,但是接下来我们要更改我们的模板,让模板引擎处理我们如何绑定到我们的控件:
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>
在这里,我们在 myValues
FormArray
中迭代我们的 FormControls
集合,对于每个控件,我们将 [formControl]
绑定到该控件,而不是FormArray
控件和 <div>{{checkboxGroup.controls['myValues'].value}}</div>
生成 true,false,true
,同时还使您的模板语法不那么手动。
你可以用这个例子:http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview来四处看看
在 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 的函数。
单击时创建一个事件,然后手动将 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 表单更改为 true 或 false 之后,它会捕获事件,如果它是 true,我将名称更改为复选框所代表的名称,如果需要,它也会评估为 true如果它也在检查 true/false。
如果您正在寻找 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 数组用于设置父表单中 'countries' 的控件。
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>
加上我的 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)
}
我的解决方案 - 使用 Material View
解决了 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 数组的表单保存到 save/update。
Will be happy to have any remarks for improvement.
在 Angular 6 中执行此操作比在以前的版本中要容易得多,即使复选框信息是从 API.
异步填充的
首先要意识到的是,由于 Angular 6 的 keyvalue
管道,我们不再需要使用 FormArray
,而是可以嵌套一个 FormGroup
.
首先,将 FormBuilder 传递给构造函数
constructor(
private _formBuilder: FormBuilder,
) { }
然后初始化我们的表单。
ngOnInit() {
this.form = this._formBuilder.group({
'checkboxes': this._formBuilder.group({}),
});
}
当我们的复选框选项数据可用时,对其进行迭代,我们可以将其作为命名 FormControl
直接推入嵌套 FormGroup
,而无需依赖数字索引查找数组。
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>
如果您想使用 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
管理清单输入的 model/state。该模型允许您将当前状态映射到您需要的任何值格式。
型号:
{
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"
查看演示
TL;DR
- 我更喜欢使用 FormGroup 来填充复选框列表
- 编写自定义验证程序以检查至少一个复选框 selected
- 工作示例https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
这有时也让我印象深刻,所以我尝试了 FormArray 和 FormGroup 两种方法。
大多数时候,复选框列表填充在服务器上,我通过 API 接收到它。但有时您会有一组带有预定义值的静态复选框。对于每个用例,将使用相应的 FormArray 或 FormGroup。
Basically FormArray
is a variant of FormGroup
. The key difference is that its data gets serialized as an array (as opposed to being serialized as an object in case of FormGroup). This might be especially useful when you don’t know how many controls will be present within the group, like dynamic forms.
为了简单起见,假设您有一个简单的创建产品表单
- 一个必需的产品名称文本框。
- 来自 select 的类别列表,需要至少检查一个。假设将从服务器检索列表。
首先,我设置了一个只有产品名称 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
将 return 给我一个 FormArray。它还将 selected 值的列表作为参数,因此如果您想重用该表单来编辑数据,它可能会有所帮助。用于创建新的产品形态,暂不适用。
注意到当您尝试访问 formArray 值时。它看起来像 [false, true, true]
。要获得 selected 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不同的是它将表单数据存储为对象,这需要一个键和一个表单控件。所以最好将key设置为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"]
。我还必须写一个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;
}
3。用于检查至少一个复选框的自定义验证器已 selected
我让验证器检查至少 X 个复选框 selected,默认情况下它只会检查一个复选框。
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;
};
}
我在这里没有看到一个解决方案可以最大程度地使用反应形式完全回答这个问题,所以这是我的解决方案。
总结
这是详细解释的要点以及 StackBlitz 示例。
- 对复选框使用
FormArray
并初始化表单。
valueChanges
observable 非常适合您希望表单显示某些内容但在组件中存储其他内容的情况。将 true
/false
值映射到此处所需的值。
- 在提交时过滤掉
false
个值。
- 取消订阅
valueChanges
observable。
StackBlitz example
详细解释
使用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
的变化,然后 map
从 FormArray
接收到的 true
/false
值到所需的值数据。请注意,这不会更改复选框的选择,因为传递给复选框的任何 truthy 值都会将其标记为已选中,反之亦然。
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
数组和 returns value
,以防复选框标记为 true
,否则它 returns false
。 emitEvent: false
在这里很重要,因为在没有它的情况下设置 FormArray
值会导致 valueChanges
发出一个事件,从而形成无限循环。通过将 emitEvent
设置为 false
,我们确保当我们在此处设置值时 valueChanges
observable 不会发射。
过滤掉错误值
我们不能直接过滤 FormArray
中的 false
值,因为这样做会弄乱模板,因为它们绑定到复选框。因此,最好的解决方案是在提交期间过滤掉 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
.
中过滤掉 falsy 值
取消订阅 valueChanges
最后,别忘了退订valueChanges
ngOnDestroy() {
this.subscription.unsubscribe();
}
注意:有一种特殊情况,valueChanges
中的FormArray
不能设置值,即复选框值设置为数字 0
。这将使该复选框看起来无法选中,因为选中该复选框会将 FormControl
设置为数字 0
(一个虚假值),从而使其保持未选中状态。最好不要使用数字 0
作为值,但如果需要,您必须有条件地将 0
设置为某个真实值,比如字符串 '0'
或只是普通的 true
然后在提交时,将其转换回数字 0
。
StackBlitz example
StackBlitz 也有用于将默认值传递给复选框的代码,以便它们在 UI.
中标记为已选中
我能够使用 FormGroups 的 FormArray 来完成此操作。 FormGroup 由两个控件组成。一个用于数据,一个用于存储检查的布尔值。
TS
options: options[] = [{id: 1, text: option1}, {id: 2, text: option2}];
this.fb.group({
options: this.fb.array([])
})
populateFormArray() {
this.options.forEach(option => {
let checked = ***is checked logic here***;
this.checkboxGroup.get('options').push(this.createOptionGroup(option, checked))
});
}
createOptionGroup(option: Option, checked: boolean) {
return this.fb.group({
option: this.fb.control(option),
checked: this.fb.control(checked)
});
}
HTML
这允许您遍历选项并绑定到相应的选中控件。
<form [formGroup]="checkboxGroup">
<div formArrayName="options" *ngFor="let option of options; index as i">
<div [formGroupName]="i">
<input type="checkbox" formControlName="checked" />
{{ option.text }}
</div>
</div>
</form>
输出
表单returns数据在表单{option: Option, checked: boolean}[]
.
您可以使用以下代码获取选中选项的列表
this.checkboxGroup.get('options').value.filter(el => el.checked).map(el => el.option);
组件:
formGroup: FormGroup;
games = [
{ keyword: 'hots', score: 9 },
{ keyword: 'xcom', score: 9 },
{ keyword: 'fallout', score: 8 }
];
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.formGroup = this.fb.group(
this.games.reduce((obj, game) => {
obj[game.keyword] = [false];
return obj;
}, {})
);
const enabledGames$ = this.formGroup.valueChanges.pipe(
map(value =>
Object.entries(value)
.filter(([key, enabled]) => enabled)
.map(([key]) =>
this.games.find(({ keyword }) => keyword === key)
)
)
);
}
模板:
<form [formGroup]="formGroup">
<div *ngFor="let control of formGroup.controls | keyvalue">
<input
type="checkbox"
[formControlName]="control.key">
<label>
{{ control.key }}
</label>
</div>
</form>
显然,这是一个非常普遍的问题,没有人有“完美”的解决方案。我相信我能够提出一个非常优雅的解决方案,使用面向对象来扩展 FormGroup 的功能。
想要API
在单个对象中我希望能够拥有:
- 每个复选框的表单控件
- 每个复选框的标签和值
- 所有选中复选框的值
所以HTML结构可以这么简单:
<div *ngFor="let item of checkboxGroup.items">
<input type="checkbox" [id]="item.value" [formControl]="item.control">
<label [for]="item.value">{{ item.label }}</label>
</div>
打字稿部分可以这么简单:
checkboxGroup.value; // return the list of selected item values
checkboxGroup.control.valid; // return if there's at least one checked value
解决方案
正如您在 HTML 部分看到的那样,checkboxGroup
需要是具有至少三个属性的 class:
- 项(每个项都是一个带有值、标签和 FormControl 的复选框)
- value(获取所有选中的项目)
- control(获取FormArray控件)
所以 class 会像:
// # This represents a single checkbox item
class CheckboxItemControl {
label: string; // value to be shown in the UI
value: string; // value to be saved in backend
control: FormControl;
constructor({ label, value, defaultValue = false }: { label: string; value: string; defaultValue?: boolean }) {
this.label = label;
this.value = value;
this.control = new FormControl(defaultValue || false);
}
get selected(): boolean {
return Boolean(this.control.value);
}
}
// # This represents a checkbox group, with several items
class CheckboxGroupControl {
name?: string; // name of the checkbox group
items: CheckboxItemControl[];
control: FormArray;
constructor(name: string, items: CheckboxItemControl[]) {
this.name = name;
this.items = items;
this.control = new FormArray(this.getAllItemsControls(), CheckboxGroupControl.emptyArrayFormValidator);
}
get value(): string[] {
return this.selectedItems.map(item => item.value);
}
private get selectedItems(): CheckboxItemControl[] {
return this.items.filter(item => item.selected);
}
private getAllItemsControls(): FormControl[] {
return this.items.map(item => item.control);
}
private static emptyArrayFormValidator(control: FormControl) {
const valid = (control.value as boolean[]).some(Boolean);
// @todo improve error message
return valid ? null : {
error: 'empty'
};
}
}
您可以看到每个 class 如何公开一个简单的 API(object.value
和 object.control
),让您轻松获得所需的一切。
用法
让我们实际看看它是如何工作的:
HTML
<div *ngFor="let item of checkboxGroup.items">
<input type="checkbox" [id]="item.value" [formControl]="item.control">
<label [for]="item.value">{{ item.label }}</label>
</div>
打字稿
checkboxGroup;
ngOnInit() {
this.createFormInputs();
}
private createFormInputs() {
const checkboxItems = [
new CheckboxItemControl({ value: 'checkbox-1', label: 'Checkbox 1' }),
new CheckboxItemControl({ value: 'checkbox-2', label: 'Checkbox 2' }),
new CheckboxItemControl({ value: 'checkbox-3', label: 'Checkbox 3', defaultValue: true })
];
this.checkboxGroup = new CheckboxGroupControl('name_of_group', checkboxItems);
this.form = new FormGroup({
checkbox: this.checkboxGroup.control
});
// this.checkboxGroup.value returns ['checkbox-1', ...] for the selected checkboxes
// this.checkboxGroup.valid returns if there's any checkbox selected
// this.form.valid returns if the whole form is valid. Which is useful if you include others checkbox groups
}
其他资源
- This article 启发了我想出这个解决方案。
模板
<div>
<input name="fruits" type="checkbox" value="orange" (change)="change($event)">
<input name="fruits" type="checkbox" value="apple" (change)="change($event)">
<input name="fruits" type="checkbox" value="banana" (change)="change($event)">
</div>
组件
formGroup = this.formBuilder.group(
{
fruits: [[]] //["Orange","Banana",...]
})
change(event: Event) {
let target = (event.target as HTMLInputElement);
let array = (this.formGroup.get(target.name)?.value as Array);
if (target.checked && !array.find(element => {
return (element === target.value);
})) {
array.push(target.value)// element not exists, push (check)
}
else {
array.splice(array.findIndex(element => {
return (element === target.value);//delete element (uncheck)
}), 1)
}
}
这就是我的做法,尽管我总是使用 Angular Material List
https://material.angular.io/components/list/overview
这些任务的一切都来自工厂
双向绑定
my.component.html
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
<div formGroupName="options">
<mat-checkbox formControlName="myVal1">My Value 1</mat-checkbox>
<mat-checkbox formControlName="myVal2">My Value 2</mat-checkbox>
</div>
<button type="submit">Submit</button>
</form>
my.component.ts
export class ClientStatementReportComponent implements OnInit {
formGroup: FormGroup;
ngOnInit(): void {
this.formGroup = new FormGroup({
options: new FormGroup({
myVal1: new FormControl(false),
myVal2: new FormControl(false)
}),
});
}
onSubmit() {
const options = this.formGroup.value.options;
const result = Object.keys(options).filter(key => options[key])
// is array of checked elements e.g. ["myVal1"]
}
}
单向绑定(形成状态)
my.component.html
<form [formGroup]="formGroup">
<mat-checkbox value="val-1" (change)="selectOption($event)">Value 1</mat-checkbox>
<mat-checkbox value="val-2" (change)="selectOption($event)">Value 2</mat-checkbox>
</form>
my.component.ts
export class MyComponent implements OnInit {
formGroup: FormGroup;
ngOnInit(): void {
this.formGroup = new FormGroup({
options: new FormControl([]),
});
}
selectOption($event: MatCheckboxChange) {
const value = $event.source.value;
const optionControl = this.formGroup.controls['options']
const options = optionControl.value as [];
if(checked){
optionControl.setValue([...options, value])
} else {
optionControl.setValue(options.filter(option => option !== value))
}
}
}
给定绑定到相同 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', ...]
模板部分:-
<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));
}
}
}
这里是使用FormArray
https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
首先,我们将使用 FormBuilder
或新建 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
});
做起来很容易,但是接下来我们要更改我们的模板,让模板引擎处理我们如何绑定到我们的控件:
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>
在这里,我们在 myValues
FormArray
中迭代我们的 FormControls
集合,对于每个控件,我们将 [formControl]
绑定到该控件,而不是FormArray
控件和 <div>{{checkboxGroup.controls['myValues'].value}}</div>
生成 true,false,true
,同时还使您的模板语法不那么手动。
你可以用这个例子:http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview来四处看看
在 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 的函数。
单击时创建一个事件,然后手动将 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 表单更改为 true 或 false 之后,它会捕获事件,如果它是 true,我将名称更改为复选框所代表的名称,如果需要,它也会评估为 true如果它也在检查 true/false。
如果您正在寻找 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 数组用于设置父表单中 'countries' 的控件。
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>
加上我的 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)
}
我的解决方案 - 使用 Material View
解决了 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 数组的表单保存到 save/update。
Will be happy to have any remarks for improvement.
在 Angular 6 中执行此操作比在以前的版本中要容易得多,即使复选框信息是从 API.
异步填充的首先要意识到的是,由于 Angular 6 的 keyvalue
管道,我们不再需要使用 FormArray
,而是可以嵌套一个 FormGroup
.
首先,将 FormBuilder 传递给构造函数
constructor(
private _formBuilder: FormBuilder,
) { }
然后初始化我们的表单。
ngOnInit() {
this.form = this._formBuilder.group({
'checkboxes': this._formBuilder.group({}),
});
}
当我们的复选框选项数据可用时,对其进行迭代,我们可以将其作为命名 FormControl
直接推入嵌套 FormGroup
,而无需依赖数字索引查找数组。
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>
如果您想使用 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
管理清单输入的 model/state。该模型允许您将当前状态映射到您需要的任何值格式。
型号:
{
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"
TL;DR
- 我更喜欢使用 FormGroup 来填充复选框列表
- 编写自定义验证程序以检查至少一个复选框 selected
- 工作示例https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
这有时也让我印象深刻,所以我尝试了 FormArray 和 FormGroup 两种方法。
大多数时候,复选框列表填充在服务器上,我通过 API 接收到它。但有时您会有一组带有预定义值的静态复选框。对于每个用例,将使用相应的 FormArray 或 FormGroup。
Basically
FormArray
is a variant ofFormGroup
. The key difference is that its data gets serialized as an array (as opposed to being serialized as an object in case of FormGroup). This might be especially useful when you don’t know how many controls will be present within the group, like dynamic forms.
为了简单起见,假设您有一个简单的创建产品表单
- 一个必需的产品名称文本框。
- 来自 select 的类别列表,需要至少检查一个。假设将从服务器检索列表。
首先,我设置了一个只有产品名称 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
将 return 给我一个 FormArray。它还将 selected 值的列表作为参数,因此如果您想重用该表单来编辑数据,它可能会有所帮助。用于创建新的产品形态,暂不适用。
注意到当您尝试访问 formArray 值时。它看起来像 [false, true, true]
。要获得 selected 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不同的是它将表单数据存储为对象,这需要一个键和一个表单控件。所以最好将key设置为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"]
。我还必须写一个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;
}
3。用于检查至少一个复选框的自定义验证器已 selected
我让验证器检查至少 X 个复选框 selected,默认情况下它只会检查一个复选框。
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;
};
}
我在这里没有看到一个解决方案可以最大程度地使用反应形式完全回答这个问题,所以这是我的解决方案。
总结
这是详细解释的要点以及 StackBlitz 示例。
- 对复选框使用
FormArray
并初始化表单。 valueChanges
observable 非常适合您希望表单显示某些内容但在组件中存储其他内容的情况。将true
/false
值映射到此处所需的值。- 在提交时过滤掉
false
个值。 - 取消订阅
valueChanges
observable。
StackBlitz example
详细解释
使用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
的变化,然后 map
从 FormArray
接收到的 true
/false
值到所需的值数据。请注意,这不会更改复选框的选择,因为传递给复选框的任何 truthy 值都会将其标记为已选中,反之亦然。
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
数组和 returns value
,以防复选框标记为 true
,否则它 returns false
。 emitEvent: false
在这里很重要,因为在没有它的情况下设置 FormArray
值会导致 valueChanges
发出一个事件,从而形成无限循环。通过将 emitEvent
设置为 false
,我们确保当我们在此处设置值时 valueChanges
observable 不会发射。
过滤掉错误值
我们不能直接过滤 FormArray
中的 false
值,因为这样做会弄乱模板,因为它们绑定到复选框。因此,最好的解决方案是在提交期间过滤掉 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();
}
注意:有一种特殊情况,valueChanges
中的FormArray
不能设置值,即复选框值设置为数字 0
。这将使该复选框看起来无法选中,因为选中该复选框会将 FormControl
设置为数字 0
(一个虚假值),从而使其保持未选中状态。最好不要使用数字 0
作为值,但如果需要,您必须有条件地将 0
设置为某个真实值,比如字符串 '0'
或只是普通的 true
然后在提交时,将其转换回数字 0
。
StackBlitz example
StackBlitz 也有用于将默认值传递给复选框的代码,以便它们在 UI.
中标记为已选中我能够使用 FormGroups 的 FormArray 来完成此操作。 FormGroup 由两个控件组成。一个用于数据,一个用于存储检查的布尔值。
TS
options: options[] = [{id: 1, text: option1}, {id: 2, text: option2}];
this.fb.group({
options: this.fb.array([])
})
populateFormArray() {
this.options.forEach(option => {
let checked = ***is checked logic here***;
this.checkboxGroup.get('options').push(this.createOptionGroup(option, checked))
});
}
createOptionGroup(option: Option, checked: boolean) {
return this.fb.group({
option: this.fb.control(option),
checked: this.fb.control(checked)
});
}
HTML
这允许您遍历选项并绑定到相应的选中控件。
<form [formGroup]="checkboxGroup">
<div formArrayName="options" *ngFor="let option of options; index as i">
<div [formGroupName]="i">
<input type="checkbox" formControlName="checked" />
{{ option.text }}
</div>
</div>
</form>
输出
表单returns数据在表单{option: Option, checked: boolean}[]
.
您可以使用以下代码获取选中选项的列表
this.checkboxGroup.get('options').value.filter(el => el.checked).map(el => el.option);
组件:
formGroup: FormGroup;
games = [
{ keyword: 'hots', score: 9 },
{ keyword: 'xcom', score: 9 },
{ keyword: 'fallout', score: 8 }
];
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.formGroup = this.fb.group(
this.games.reduce((obj, game) => {
obj[game.keyword] = [false];
return obj;
}, {})
);
const enabledGames$ = this.formGroup.valueChanges.pipe(
map(value =>
Object.entries(value)
.filter(([key, enabled]) => enabled)
.map(([key]) =>
this.games.find(({ keyword }) => keyword === key)
)
)
);
}
模板:
<form [formGroup]="formGroup">
<div *ngFor="let control of formGroup.controls | keyvalue">
<input
type="checkbox"
[formControlName]="control.key">
<label>
{{ control.key }}
</label>
</div>
</form>
显然,这是一个非常普遍的问题,没有人有“完美”的解决方案。我相信我能够提出一个非常优雅的解决方案,使用面向对象来扩展 FormGroup 的功能。
想要API
在单个对象中我希望能够拥有:
- 每个复选框的表单控件
- 每个复选框的标签和值
- 所有选中复选框的值
所以HTML结构可以这么简单:
<div *ngFor="let item of checkboxGroup.items">
<input type="checkbox" [id]="item.value" [formControl]="item.control">
<label [for]="item.value">{{ item.label }}</label>
</div>
打字稿部分可以这么简单:
checkboxGroup.value; // return the list of selected item values
checkboxGroup.control.valid; // return if there's at least one checked value
解决方案
正如您在 HTML 部分看到的那样,checkboxGroup
需要是具有至少三个属性的 class:
- 项(每个项都是一个带有值、标签和 FormControl 的复选框)
- value(获取所有选中的项目)
- control(获取FormArray控件)
所以 class 会像:
// # This represents a single checkbox item
class CheckboxItemControl {
label: string; // value to be shown in the UI
value: string; // value to be saved in backend
control: FormControl;
constructor({ label, value, defaultValue = false }: { label: string; value: string; defaultValue?: boolean }) {
this.label = label;
this.value = value;
this.control = new FormControl(defaultValue || false);
}
get selected(): boolean {
return Boolean(this.control.value);
}
}
// # This represents a checkbox group, with several items
class CheckboxGroupControl {
name?: string; // name of the checkbox group
items: CheckboxItemControl[];
control: FormArray;
constructor(name: string, items: CheckboxItemControl[]) {
this.name = name;
this.items = items;
this.control = new FormArray(this.getAllItemsControls(), CheckboxGroupControl.emptyArrayFormValidator);
}
get value(): string[] {
return this.selectedItems.map(item => item.value);
}
private get selectedItems(): CheckboxItemControl[] {
return this.items.filter(item => item.selected);
}
private getAllItemsControls(): FormControl[] {
return this.items.map(item => item.control);
}
private static emptyArrayFormValidator(control: FormControl) {
const valid = (control.value as boolean[]).some(Boolean);
// @todo improve error message
return valid ? null : {
error: 'empty'
};
}
}
您可以看到每个 class 如何公开一个简单的 API(object.value
和 object.control
),让您轻松获得所需的一切。
用法
让我们实际看看它是如何工作的:
HTML
<div *ngFor="let item of checkboxGroup.items">
<input type="checkbox" [id]="item.value" [formControl]="item.control">
<label [for]="item.value">{{ item.label }}</label>
</div>
打字稿
checkboxGroup;
ngOnInit() {
this.createFormInputs();
}
private createFormInputs() {
const checkboxItems = [
new CheckboxItemControl({ value: 'checkbox-1', label: 'Checkbox 1' }),
new CheckboxItemControl({ value: 'checkbox-2', label: 'Checkbox 2' }),
new CheckboxItemControl({ value: 'checkbox-3', label: 'Checkbox 3', defaultValue: true })
];
this.checkboxGroup = new CheckboxGroupControl('name_of_group', checkboxItems);
this.form = new FormGroup({
checkbox: this.checkboxGroup.control
});
// this.checkboxGroup.value returns ['checkbox-1', ...] for the selected checkboxes
// this.checkboxGroup.valid returns if there's any checkbox selected
// this.form.valid returns if the whole form is valid. Which is useful if you include others checkbox groups
}
其他资源
- This article 启发了我想出这个解决方案。
模板
<div>
<input name="fruits" type="checkbox" value="orange" (change)="change($event)">
<input name="fruits" type="checkbox" value="apple" (change)="change($event)">
<input name="fruits" type="checkbox" value="banana" (change)="change($event)">
</div>
组件
formGroup = this.formBuilder.group( { fruits: [[]] //["Orange","Banana",...] }) change(event: Event) { let target = (event.target as HTMLInputElement); let array = (this.formGroup.get(target.name)?.value as Array); if (target.checked && !array.find(element => { return (element === target.value); })) { array.push(target.value)// element not exists, push (check) } else { array.splice(array.findIndex(element => { return (element === target.value);//delete element (uncheck) }), 1) } }
这就是我的做法,尽管我总是使用 Angular Material List
https://material.angular.io/components/list/overview
这些任务的一切都来自工厂
双向绑定
my.component.html
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
<div formGroupName="options">
<mat-checkbox formControlName="myVal1">My Value 1</mat-checkbox>
<mat-checkbox formControlName="myVal2">My Value 2</mat-checkbox>
</div>
<button type="submit">Submit</button>
</form>
my.component.ts
export class ClientStatementReportComponent implements OnInit {
formGroup: FormGroup;
ngOnInit(): void {
this.formGroup = new FormGroup({
options: new FormGroup({
myVal1: new FormControl(false),
myVal2: new FormControl(false)
}),
});
}
onSubmit() {
const options = this.formGroup.value.options;
const result = Object.keys(options).filter(key => options[key])
// is array of checked elements e.g. ["myVal1"]
}
}
单向绑定(形成状态)
my.component.html
<form [formGroup]="formGroup">
<mat-checkbox value="val-1" (change)="selectOption($event)">Value 1</mat-checkbox>
<mat-checkbox value="val-2" (change)="selectOption($event)">Value 2</mat-checkbox>
</form>
my.component.ts
export class MyComponent implements OnInit {
formGroup: FormGroup;
ngOnInit(): void {
this.formGroup = new FormGroup({
options: new FormControl([]),
});
}
selectOption($event: MatCheckboxChange) {
const value = $event.source.value;
const optionControl = this.formGroup.controls['options']
const options = optionControl.value as [];
if(checked){
optionControl.setValue([...options, value])
} else {
optionControl.setValue(options.filter(option => option !== value))
}
}
}