如何将选中的值放入 Angular 模板驱动表单 (ngModel) 的数组中
How to get checked values into an array for Angular Template Driven Form (ngModel)
我正在尝试使用模板驱动的表单,将 html 中的模型绑定到 Person 的新实例。我未能成功为我的模型上的单个数组 属性 创建复选框的正确绑定。
想法是数据将来自 api 或其他来源,通过 *ngFor
动态呈现复选框并将选择的内容绑定到 Person 模型属性 这将是一个数字数组。例如:
class Person {
firstName: string;
someCheckboxPropList: number[];
}
数据可以是任何东西
const dynamicData = [
{ name: 'name1', value: 1 },
{ name: 'name2', value: 2 }
];
如果要检查两个值并且只检查第二个值,我的预期输出将类似于 [ 1, 2 ]
[ 2 ]
。
这是 PersonComponent.ts 文件的示例
@Component({ ... })
export class PersonComponent {
submitted = false;
model = new Person();
constructor() { }
onSubmit(form) {
this.submitted = true;
console.log(form);
}
}
以及组件 html 文件所在的位置。
<form (ngSubmit)="onSubmit(form)" #form="ngForm">
<input type="text" [(ngModel)] name="person.firstName">
<div *ngFor="let dataItem of dynamicData" >
<input
type="checkbox"
ngModel
name="dynamicData"
[value]="dataItem.value">
<label>{{dataItem.name}}</label>
</div>
</form>
这不起作用(无论如何都是示例代码)。
这个想法有两个东西:Person 和 PersonForm,所以,例如
person={firstName:"Jessy",props:[1,2]
//but
personForm={firstName:"Jessy",props:[true,true]
所以,做两个函数
createForm(person) {
return {
firstName: person.firstName,
props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
}
}
retrieveData(personForm) {
let props: number[] = [];
personForm.props.forEach((v, index) => {
if (v)
props.push(this.dynamicData[index].value)
}
)
return {
firstName: personForm.firstName,
props: props
}
}
好吧,我们已经拥有了所需的一切。当我们收到一个人时,创建一个 personForm,它是我们在表单中更改的数据。在提交中,只需调用 retrieveData 即可获取 person 的值。
当我们让一个人创建一个 personForm 时,例如
this.service.getPerson().subscribe(person=>
{
this.personForm=createForm(person)
}
)
我们的形式
<form *ngIf="personForm" (submit)="sendData(personForm)">
<input name="firtName" [(ngModel)]="personForm.firstName">
<div *ngFor="let item of dynamicData;let i=index">
<input name="{{'prop'+i}}"
type="checkBox" [(ngModel)]="personForm.props[i]">{{item.name}}
</div>
<button>Submit</button>
</form>
{{personForm|json}}<br/>
{{retrieveData(personForm)|json}}
还有我们的 sendData 函数
sendData(personForm)
{
console.log(this.retrieveData(personForm))
}
我做了一个simple stackblitz
更新
注意:我们可以使用 spred 运算符来设置属性,所以
createForm(person) {
return {
...person, //All the properties of person, but
props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
}
}
retrieveData(personForm) {
let props: number[] = [];
personForm.props.forEach((v, index) => {
if (v)
props.push(this.dynamicData[index].value)
}
)
return {
..personForm, //all the properties of personForm, but
props: props
}
}
注意 2:在 "real world" 中,人员离开服务。考虑服务 get/received 的想法 "personForm" 并将要转换的功能放在服务
中
//in service
getPerson()
{
return this.httpClient.get("...").map(res=>this.createForm(res))
}
savePerson(personForm)
{
return this.httpClient.post("...",this.retrieveData(personForm))
}
如果需要,我们可以制作自定义表单控件。
在这种情况下,我们需要作为输入、源和源的列 - 第一个将是键,第二个是出现的文本。
我做了一个stackblitz
.html 将是
<check-box-group name="props" [(ngModel)]="person.props"
[source]="dynamicData" cols="value,name" >
</check-box-group>
该组件是典型的自定义表单控件
@Component({
selector: 'check-box-group',
template: `
<div class="form-check" *ngFor="let item of source;let i=index">
<input class="form-check-input" id="{{_name+''+i}}"
type="checkBox" [ngModel]="_selectedItems[i]"
(ngModelChange)="setValue($event,i)">
<label class="form-check-label" for="{{_name+''+i}}">
{{item[_col]}}
</label>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true
}
]
})
export class CheckBoxGroupComponent implements ControlValueAccessor {
@Input() source;
@Input()
set cols(value:string){ //cols is a string separated by commas
//e.g. "value,text", the "key" will be "value" and show the text
let _cols=value.split(',')
this._key = _cols[0];
this._col = _cols[1]
}
_selectedItems: any[] = [];
_key: string;
_col: string;
_name:string="";
onChange;
onTouched;
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name')
this._name=name?name:"ck";
}
writeValue(value: any[]): void {
this._selectedItems = this.propsToBoolean(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
}
setValue(value: boolean, index: number) {
this._selectedItems[index] = value;
this.onChange(this.booleanToProps(this._selectedItems));
}
propsToBoolean(props): any[] {
console.log(props);
return props ? this.source.map((x: any) => props.indexOf(x[this._key]) >= 0)
: this.source.map(x => false);
}
booleanToProps(propsBoolean: boolean[]) {
let props: any[] = [];
if (propsBoolean) {
propsBoolean.forEach((item, index) => {
if (item)
props.push(this.source[index][this._key])
})
}
return props;
}
}
更新: 添加验证
当我们有一个自定义表单组件并且我们想要制作一个 "validation" 时,我们有两个选择,在组件外部进行验证或在组件内部进行验证。对于第二个选项,我们必须添加作为提供者提供:NG_VALIDATORS,
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true,
}
并添加一个函数验证
validate(control: AbstractControl): ValidationErrors | null{
...your logic here.., e.g.
if (!this._selectedItems.find(x=>x))
return {error:"you must select one option at last"}
return null
}
好吧,还有一件事我们必须做,那就是决定何时触摸我们的自定义控件。请记住,当控件在接收到焦点后失去焦点时被触摸。我们可以在我们的复选框的(模糊)中完成它(或者将控件包含在 div 和 tabindex=0)
<input type="checkbox" .... (blur)="onTouched()">
最后一步是让我们给控件添加一个属性。我喜欢这样,如果我们添加一个属性 isRequired,则检查错误,否则不检查。所以我们添加一个新的 属性 _isRequired 并且在构造函数中检查是否具有属性
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name');
this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
this._name=name?name:"ck"; //<--this is necesary for give value to
//for="..." in label
}
我们的验证考虑到了这一点
validate(control: AbstractControl): ValidationErrors | null{
if (!this._isRequired)
return null;
....
}
注意:我更新了自定义控件(我们添加了属性 [customClass])
我正在尝试使用模板驱动的表单,将 html 中的模型绑定到 Person 的新实例。我未能成功为我的模型上的单个数组 属性 创建复选框的正确绑定。
想法是数据将来自 api 或其他来源,通过 *ngFor
动态呈现复选框并将选择的内容绑定到 Person 模型属性 这将是一个数字数组。例如:
class Person {
firstName: string;
someCheckboxPropList: number[];
}
数据可以是任何东西
const dynamicData = [
{ name: 'name1', value: 1 },
{ name: 'name2', value: 2 }
];
如果要检查两个值并且只检查第二个值,我的预期输出将类似于 [ 1, 2 ]
[ 2 ]
。
这是 PersonComponent.ts 文件的示例
@Component({ ... })
export class PersonComponent {
submitted = false;
model = new Person();
constructor() { }
onSubmit(form) {
this.submitted = true;
console.log(form);
}
}
以及组件 html 文件所在的位置。
<form (ngSubmit)="onSubmit(form)" #form="ngForm">
<input type="text" [(ngModel)] name="person.firstName">
<div *ngFor="let dataItem of dynamicData" >
<input
type="checkbox"
ngModel
name="dynamicData"
[value]="dataItem.value">
<label>{{dataItem.name}}</label>
</div>
</form>
这不起作用(无论如何都是示例代码)。
这个想法有两个东西:Person 和 PersonForm,所以,例如
person={firstName:"Jessy",props:[1,2]
//but
personForm={firstName:"Jessy",props:[true,true]
所以,做两个函数
createForm(person) {
return {
firstName: person.firstName,
props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
}
}
retrieveData(personForm) {
let props: number[] = [];
personForm.props.forEach((v, index) => {
if (v)
props.push(this.dynamicData[index].value)
}
)
return {
firstName: personForm.firstName,
props: props
}
}
好吧,我们已经拥有了所需的一切。当我们收到一个人时,创建一个 personForm,它是我们在表单中更改的数据。在提交中,只需调用 retrieveData 即可获取 person 的值。
当我们让一个人创建一个 personForm 时,例如
this.service.getPerson().subscribe(person=>
{
this.personForm=createForm(person)
}
)
我们的形式
<form *ngIf="personForm" (submit)="sendData(personForm)">
<input name="firtName" [(ngModel)]="personForm.firstName">
<div *ngFor="let item of dynamicData;let i=index">
<input name="{{'prop'+i}}"
type="checkBox" [(ngModel)]="personForm.props[i]">{{item.name}}
</div>
<button>Submit</button>
</form>
{{personForm|json}}<br/>
{{retrieveData(personForm)|json}}
还有我们的 sendData 函数
sendData(personForm)
{
console.log(this.retrieveData(personForm))
}
我做了一个simple stackblitz
更新
注意:我们可以使用 spred 运算符来设置属性,所以
createForm(person) {
return {
...person, //All the properties of person, but
props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
}
}
retrieveData(personForm) {
let props: number[] = [];
personForm.props.forEach((v, index) => {
if (v)
props.push(this.dynamicData[index].value)
}
)
return {
..personForm, //all the properties of personForm, but
props: props
}
}
注意 2:在 "real world" 中,人员离开服务。考虑服务 get/received 的想法 "personForm" 并将要转换的功能放在服务
中//in service
getPerson()
{
return this.httpClient.get("...").map(res=>this.createForm(res))
}
savePerson(personForm)
{
return this.httpClient.post("...",this.retrieveData(personForm))
}
如果需要,我们可以制作自定义表单控件。
在这种情况下,我们需要作为输入、源和源的列 - 第一个将是键,第二个是出现的文本。
我做了一个stackblitz
.html 将是
<check-box-group name="props" [(ngModel)]="person.props"
[source]="dynamicData" cols="value,name" >
</check-box-group>
该组件是典型的自定义表单控件
@Component({
selector: 'check-box-group',
template: `
<div class="form-check" *ngFor="let item of source;let i=index">
<input class="form-check-input" id="{{_name+''+i}}"
type="checkBox" [ngModel]="_selectedItems[i]"
(ngModelChange)="setValue($event,i)">
<label class="form-check-label" for="{{_name+''+i}}">
{{item[_col]}}
</label>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true
}
]
})
export class CheckBoxGroupComponent implements ControlValueAccessor {
@Input() source;
@Input()
set cols(value:string){ //cols is a string separated by commas
//e.g. "value,text", the "key" will be "value" and show the text
let _cols=value.split(',')
this._key = _cols[0];
this._col = _cols[1]
}
_selectedItems: any[] = [];
_key: string;
_col: string;
_name:string="";
onChange;
onTouched;
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name')
this._name=name?name:"ck";
}
writeValue(value: any[]): void {
this._selectedItems = this.propsToBoolean(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
}
setValue(value: boolean, index: number) {
this._selectedItems[index] = value;
this.onChange(this.booleanToProps(this._selectedItems));
}
propsToBoolean(props): any[] {
console.log(props);
return props ? this.source.map((x: any) => props.indexOf(x[this._key]) >= 0)
: this.source.map(x => false);
}
booleanToProps(propsBoolean: boolean[]) {
let props: any[] = [];
if (propsBoolean) {
propsBoolean.forEach((item, index) => {
if (item)
props.push(this.source[index][this._key])
})
}
return props;
}
}
更新: 添加验证
当我们有一个自定义表单组件并且我们想要制作一个 "validation" 时,我们有两个选择,在组件外部进行验证或在组件内部进行验证。对于第二个选项,我们必须添加作为提供者提供:NG_VALIDATORS,
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true,
}
并添加一个函数验证
validate(control: AbstractControl): ValidationErrors | null{
...your logic here.., e.g.
if (!this._selectedItems.find(x=>x))
return {error:"you must select one option at last"}
return null
}
好吧,还有一件事我们必须做,那就是决定何时触摸我们的自定义控件。请记住,当控件在接收到焦点后失去焦点时被触摸。我们可以在我们的复选框的(模糊)中完成它(或者将控件包含在 div 和 tabindex=0)
<input type="checkbox" .... (blur)="onTouched()">
最后一步是让我们给控件添加一个属性。我喜欢这样,如果我们添加一个属性 isRequired,则检查错误,否则不检查。所以我们添加一个新的 属性 _isRequired 并且在构造函数中检查是否具有属性
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name');
this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
this._name=name?name:"ck"; //<--this is necesary for give value to
//for="..." in label
}
我们的验证考虑到了这一点
validate(control: AbstractControl): ValidationErrors | null{
if (!this._isRequired)
return null;
....
}
注意:我更新了自定义控件(我们添加了属性 [customClass])