问题描述
我设法在UI上正确绑定了this.empDetails.services
的数据,正确选中了复选框,并列出了所有复选框选项。
但是,数据没有被推送到serviceFormArray,当我单击“更新”而不对复选框进行任何更改时, this.updateServicesForm.value
为空。
我必须取消选中那些已选中的复选框,然后再次对其进行检查,以便将其推送到formarray
。
我尝试了一些更改,但无济于事,有人可以建议正确的代码来存档我所需要的吗? 非常感谢。 的HTML
<form action="javascript:" [formGroup]="updateSvcForm">
<div class="row" *ngFor="let service of servicesOptions; let i=index">
<div class="col-sm-12">
<div class="checkbox-color checkbox-primary">
<input type="checkbox" id={{service.value}} [value]="service.value" (change)="onCheckChange($event)" [checked]=isSelected(service.value)>
<label for={{service.value}}>
{{service.description}}
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2"></label>
<div class="col-sm-10">
<button class="btn btn-primary m-b-0 ripple light" (click)="updateServices()">Update</button>
</div>
</div>
</form>
组件
sservicesOptions = [
{ description: '1. Sweeping', value: 'sweeping' },
{ description: '2. Mopping', value: 'mopping' },
{ description: '3. Windows', value: 'windows' },
{ description: '4. Washing Clothes', value: 'washingclothes' },
];
this.updateSvcForm= this.fb.group({
sservices: new FormArray([]),
});
onCheckChange(event) {
const sservicesFormArray: FormArray =
this.updateSvcForm.get('sservices') as FormArray;
if (event.target.checked) {
sservicesFormArray.push(new FormControl(event.target.value));
}
else {
let i: number = 0;
sservicesFormArray.controls.forEach((ctrl: FormControl) => {
if (ctrl.value == event.target.value) {
sservicesFormArray.removeAt(i);
return;
}
i++;
});
}
}
isSelected(sserviceOption) {
return this.empDetails.services.indexOf(serviceOption) >= 0;
}
console.log(this.updateSvcForm.value);
}
来自this.empDetails.services API返回的数据
sservices: Array(2)
0: "mopping"
1: "washingclothes"
length: 2
__proto__: Array(0)
1楼
这样做的原因是,您正在使用checked
标记来标记应选中的复选框,它们与您的表单数组没有关联,因此,如果您不触摸复选框,则formarray将正确为空。
我可以提供几个选项来解决此问题...还有以下更改:
更改功能可以更改为:
onCheckChange(event) {
if (event.target.checked) {
this.ssArray.push(this.fb.control(event.target.value));
}
else {
this.ssArray.removeAt(this.ssArray.value.findIndex(x => x === event.target.value))
}
}
不管您如何操作,您的方式也可以正常工作:)我也喜欢使用FormBuilder
(在这里注入为fb
)。
我喜欢在这种情况下使用吸气剂:
get ssArray() {
return this.updateSvcForm.get('sservices') as FormArray;
}
我能想到的选择:
-
将
checked
属性添加到数组sservicesOptions
的对象 -
保留您的
isSelected
函数,但最初将选择的选项添加到您的formarray中
我最喜欢选项1,因此向对象添加一个checked
属性:
servicesOptions = [
{ description: '1. Sweeping', value: 'sweeping', checked: false },
{ description: '2. Mopping', value: 'mopping', checked: false },
{ description: '3. Windows', value: 'windows', checked: false },
{ description: '4. Washing Clothes', value: 'washingclothes', checked: false },
];
然后,在构建表单时,更改应预先选择的状态,并将其添加到表单数组中:
constructor(private fb: FormBuilder) {
this.updateSvcForm = this.fb.group({
sservices: this.fb.array([]),
});
// change the checked status for the checkboxes that should be checked
this.servicesOptions.map(x =>
this.empDetails.services.indexOf(x) >= 0 ? x.checked = true : x.checked = false)
// initially set the selected form controls to the formarray
this.empDetails.services.map((x) => this.ssArray.push(this.fb.control(x)))
}
然后,您可以在模板中添加[checked]="service.checked"
。
选项2:
保持checked
函数不变,只记得将预选择的值添加到formarray中。
我不太喜欢这个选项,因为例如,我们最终在模板中调用了一个函数,实际上不建议这样做。
但是无论如何,将代码保持与现在相同,只需将初始值添加到formarray即可:
this.updateSvcForm = this.fb.group({
sservices: this.fb.array([]),
});
// add the intial values to the formarray:
this.empDetails.services.map((x) => this.ssArray.push(this.fb.control(x)))
我在函数内部添加了console.log,以显示其调用方式。 像这样的演示可以,但是如果您的表格很大,我真的要警告您使用此解决方案。
第三种选择是,将所有值实际设置为表单数组,然后仅切换复选框的布尔值,但这将需要对代码进行一些重构,我不知道您是否要这样做。 但是也有这种选择。
2楼
您忘记设置Array sservices
形式的新值:
onCheckChange(event) {
const sservicesFormArray: FormArray =
this.updateSvcForm.get('sservices') as FormArray;
if (event.target.checked) {
sservicesFormArray.push(new FormControl(event.target.value));
}
else {
let i: number = 0;
sservicesFormArray.controls.forEach((ctrl: FormControl) => {
if (ctrl.value == event.target.value) {
sservicesFormArray.removeAt(i);
break;
}
i++;
});
}
// set the new value of sservices form array
this.updateSvcForm.setControl('sservices', sservicesFormArray);
}
3楼
“简便”的方法是创建一个值为true / false的FormArray。 参见的示例
更新 :更正一些错误
您使用数据和sservicesOptions填充formArray
getFormArrayService(data:any[]):FormArray
{
//e.g. data=['mopping','washingclothes']
// return a FormArray and the value will be [false,true,false,true]
//if data=null, return a FormArray [false,false,false,false]
return new FormArray(
this.sservicesOptions.map(x=>new FormControl(data?data.find(dat=>dat==x.value)?true:false:false))
)
}
因此,您可以在ngInit中使
ngOnInit()
{
this.updateSvcForm=new FormGroup({
sservices:this.getFormArrayService(null)
})
}
并在提交表格时,转换价值
submit(updateSvcForm)
{
if (updateSvcForm.valid)
{
let services:string[]=[];
updateSvcForm.value.sservices.forEach((x,index)=>
{
if (x)
services.push(this.sservicesOptions.value)
})
const result={
...updateSvcForm.value, //all value of the form but
sservices:services
}
console.log(result)
}
}
.html变成像
<form *ngIf="updateSvcForm" [formGroup]="updateSvcForm" (submit)="submit(updateSvcForm)">
<div formArrayName="sservices">
<div *ngFor="let control of updateSvcForm.get('sservices').controls;let i=index">
<input type="checkbox" [formControlName]="i"/>
{{sservicesOptions[i].description}}
</div>
</div>
<button type="submit">submit</button>
</form>
{{updateSvcForm?.value|json}}
在“不是那么容易的方法”一customFormControl,请参阅在示例
基本上,我们创建了一系列复选框,复选框中的每个更改都返回“ booleansToProp”。 在示例中,我添加了一个属性“ required”,然后如果未选中检查,则表明它无效;如果我们可以返回一个字符串,而不是一个数组,则表明isString。
@Component({
selector: 'check-box-group',
template: `
<ng-container *ngFor="let item of source;let i=index;let last=last">
<div [ngClass]="last?'form-group':''" class="form-check" >
<input type="checkbox" class="form-check-input" id="{{_name+''+i}}"
[ngModel]="_selectedItems[i]"
(ngModelChange)="setValue($event,i)" (blur)="onTouched()" >
<label class="form-check-label" for="{{_name+''+i}}">{{item[_col]}}</label>
</div>
</ng-container>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true,
}
],
styles:[`
.focused {
outline: black dotted thin;
}`
]
})
export class CheckBoxGroupComponent implements ControlValueAccessor {
@Input()
set source(value)
{
this._source=value;
//we need to know which column has the "value" and which column has the "text"
//replace all extrange character else ":" and ","
let aux=JSON.stringify(value[0]).replace(/[^\w|:|,\s]/gi, '').split(',');
this._key=aux[0].split(':')[0]
this._col=aux[1].split(':')[0]
}
get source()
{
return this._source;
}
_selectedItems: any[] = [];
_source;
_key: string;
_col: string;
_name:string="";
_isString:boolean=false;
_isRequired:boolean=false;
onChange;
onTouched;
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name');
//we store in this._isRequired if the element has an attribute "required"
this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
//idem if the element has an attribute "isString"
this._isString=el.nativeElement.getAttribute('isString')!=null?true:false;
//Is necesary give a name to the control if there're severals check-box-group
this._name=name?name:"ck";
}
writeValue(value: any[]|any): void {
this._selectedItems = this._isString?
this.propsToBoolean(value?value.split(','):""):this.propsToBoolean(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
}
//setValue is called each time you check/uncheck a checkbox
//Simple call to this.onChange with the value o the result of the
//function this.booleanToProps
setValue(value: boolean, index: number) {
this._selectedItems[index] = value;
this.onChange(this._isString?
this.booleanToProps(this._selectedItems).join(','):
this.booleanToProps(this._selectedItems));
}
validate(control: AbstractControl): ValidationErrors | null{
if (!this._isRequired)
return null;
if (!this._selectedItems.find(x=>x))
return {error:"you must select one option at last"}
return null
}
//we received an array (or a string separated by commas) and
//return an array of true/false
propsToBoolean(props): any[] {
let propsString=props?props.map(x=>''+x):null;
return props ? this.source.map((x: any) => propsString.indexOf(''+x[this._key]) >= 0)
: this.source.map(x => false);
}
//we received an array of true/false and return an array with the values
//or with teh values separated by commas
booleanToProps(propsBoolean: boolean[]) {
let props: any[] = [];
if (propsBoolean) {
propsBoolean.forEach((item, index) => {
if (item)
props.push(this.source[index][this._key])
})
}
return props;
}
}