我正在使用Angular 5.0.0和Material 5.2.2。
此表格中有两个子问题。用户以一种形式提交两次。这必须保持这种方式,因为我在这里展示了我原始形式的一个非常简化的版本。
在第一次提交后,在第二个子问题中,我验证是否有最小的一个选中复选框。如果不是,我会this.choicesSecond.setErrors({'incorrect': true});
。这个
以正确的方式禁用submit
按钮。但这会产生错误:
`ExpressionChangedAfterItHasBeenCheckedError:表达式已更改 检查后。上一个值:'true'。当前值:'false'。
我认为这与change detection
有关。如果我使用this.changeDetectorRef.detectChanges()
进行额外的更改检测,则错误消失,但提交按钮不再被禁用。
我做错了什么?
模板:
<mat-card>
<form *ngIf="myForm" [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)" novalidate>
<div *ngIf="!subQuestion">
<mat-card-header>
<mat-card-title>
<h3>Which fruit do you like most?</h3>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-radio-group formControlName="choiceFirst">
<div *ngFor="let fruit of fruits; let i=index" class="space">
<mat-radio-button [value]="fruit">{{fruit}}</mat-radio-button>
</div>
</mat-radio-group>
</mat-card-content>
</div>
<div *ngIf="subQuestion">
<mat-card-header>
<mat-card-title>
<h3>Whichs fruits do you like?</h3>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngFor="let choiceSecond of choicesSecond.controls; let i=index">
<mat-checkbox [formControl]="choiceSecond">{{fruits[i]}}</mat-checkbox>
</div>
</mat-card-content>
</div>
<mat-card-actions>
<button mat-raised-button type="submit" [disabled]="!myForm.valid">Submit</button>
</mat-card-actions>
</form>
</mat-card>
组件:
export class AppComponent {
myForm: FormGroup;
fruits: Array<string> = ["apple", "pear", "kiwi", "banana", "grape", "strawberry", "grapefruit", "melon", "mango", "plum"];
numChecked: number = 0;
subQuestion: boolean = false;
constructor(private formBuilder: FormBuilder, private changeDetectorRef: ChangeDetectorRef) { }
ngOnInit() {
this.myForm = this.formBuilder.group({
'choiceFirst': [null, [Validators.required]],
});
let choicesFormArray = this.fruits.map(fruit => { return this.formBuilder.control(false) });
this.myForm.setControl('choicesSecond', this.formBuilder.array(choicesFormArray));
this.onChangeAnswers();
}
onChangeAnswers() {
this.choicesSecond.valueChanges.subscribe(value => {
let numChecked = value.filter(item => item).length;
if (numChecked === 0 ) this.choicesSecond.setErrors({'incorrect': true});
});
}
get choicesSecond(): FormArray {
return this.myForm.get('choicesSecond') as FormArray;
};
onSubmit(submit) {
if (!this.subQuestion) {
this.subQuestion = true;
let numChecked = this.choicesSecond.controls.filter(item => item.value).length;
if (numChecked === 0 ) this.choicesSecond.setErrors({'incorrect': true});
// this.changeDetectorRef.detectChanges()
}
console.log(submit);
}
}
答案 0 :(得分:2)
问题来自this.choicesSecond.setErrors({'incorrect': true});
,当您单击提交时,您创建组件并同时更改其值。由于通过角度进行的附加检查,这在开发模式中失败。 Here is a good article about this error.
对于表单验证,您可以使用自定义验证程序an example in this post:
minLengthArray(min: number) {
return (c: AbstractControl): {[key: string]: any} => {
if (c.value.length >= min)
return null;
return { 'minLengthArray': {valid: false }};
}
}
对于步骤,当您使用角度材质时,可以使用mat-stepper。
答案 1 :(得分:0)
解决方案@ibenjelloun适用于两项调整(参见他的解决方案下的评论):
if (c.value.length >= min)
需要if (c.value.filter(item => item).length >= min)
仅过滤选中的复选框。
&#39; choicesSecond&#39;的setControl需要在第一次提交onSubmit
方法而不是ngOnInit
挂钩后完成。您还需要执行setValidators
和updateValueAndValidity
。
由于@ibenjelloun建议使用从第一个子问题到第二个子问题的额外按钮,解决方案更好,因为只有这样才能实现back
按钮。
以下是有效的最终组件:
export class AppComponent {
myForm: FormGroup;
fruits: Array<string> = ["apple", "pear", "kiwi", "banana", "grape", "strawberry", "grapefruit", "melon", "mango", "plum"];
subQuestion: boolean = false;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.myForm = this.formBuilder.group({
'choiceFirst': [null, [Validators.required]],
});
}
minLengthArray(min: number) {
return (c: AbstractControl): { [key: string]: any } => {
if (c.value.filter(item => item).length >= min)
return null;
return { 'minLengthArray': { valid: false } };
}
}
get choicesSecond(): FormArray {
return this.myForm.get('choicesSecond') as FormArray;
};
onSubmit(submit) {
if (!this.subQuestion) {
let choicesSecondFormArray = this.fruits.map(fruit => { return this.formBuilder.control(false) });
this.myForm.setControl('choicesSecond', this.formBuilder.array(choicesSecondFormArray));
this.myForm.get('choicesSecond').setValidators(this.minLengthArray(1));
this.myForm.get('choicesSecond').updateValueAndValidity();
this.subQuestion = true;
}
console.log(submit);
}
}