简体   繁体   English

Angular Reactive Form手动验证给出了ExpressionChangedAfterItHaHasBeenCheckedError

[英]Angular Reactive Form manual validate gives ExpressionChangedAfterItHasBeenCheckedError

I'm using Angular 5.0.0 and Material 5.2.2. 我正在使用Angular 5.0.0和Material 5.2.2。

This form has two sub questions in it. 该表格中有两个子问题。 The user is submitting two times in one form. 用户以一种形式提交两次。 This has to stay this way because I present here a very stripped down version of my original form. 这必须保持这种方式,因为我在这里展示了我原始表格的精简版。

After the first submit, in the second subquestion, I validate if there is minimum one checked checkbox. 在第一次提交之后,在第二个子问题中,我确认是否至少有一个选中的复选框。 If not I do an this.choicesSecond.setErrors({'incorrect': true}); 如果没有,我会执行this.choicesSecond.setErrors({'incorrect': true}); . This disables the submit button in a correct way. 这样可以正确禁用submit按钮。 But this gives an error: 但这给出了一个错误:

`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. `ExpressionChangedAfterItHaHasBeenCheckedError:表达式在检查后已更改。 Previous value: 'true'. 先前的值:“ true”。 Current value: 'false'. 当前值:“ false”。

I think it has to do with with change detection . 我认为这与change detection If I do an extra change detection with this.changeDetectorRef.detectChanges() then the error disappears but the submit button is not disabled anymore. 如果我使用this.changeDetectorRef.detectChanges()进行额外的更改检测,则错误消失,但不再禁用提交按钮。

What am I doing wrong? 我究竟做错了什么?

Template: 模板:

<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>

Component: 零件:

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);
  }

}

The issues comes from the this.choicesSecond.setErrors({'incorrect': true}); 问题来自this.choicesSecond.setErrors({'incorrect': true}); , when you click submit you create the component and at the same time change its value. ,当您单击提交时,您将创建组件并同时更改其值。 This fails in development mode because of the aditional check done by angular. 由于通过角度进行的附加检查,因此在开发模式中失败了。 Here is a good article about this error. 这是一篇有关此错误的好文章。

For form validation you can use a custom validator, 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 }};
    }
}

And for steps, as you are using angular material you could use the mat-stepper . 对于台阶,当您使用有角度的材料时,可以使用mat-stepper

The solution @ibenjelloun works well with two adjustments (see also comments under his solution): @ibenjelloun的解决方案通过两个调整可以很好地工作(另请参见其解决方案下的注释):

  1. if (c.value.length >= min) needs to be if (c.value.filter(item => item).length >= min) filtering only the checkboxes that are checked. if (c.value.length >= min)需要成为if (c.value.filter(item => item).length >= min)仅过滤选中的复选框。

  2. the setControl of 'choicesSecond' needs to be done after the first submit in onSubmit method and not the ngOnInit hook. setControl的“ choicesSecond”需要在onSubmit方法中的第一个提交之后完成,而不是ngOnInit挂钩。 Here also you need to do the setValidators and updateValueAndValidity . 在这里你也需要做setValidatorsupdateValueAndValidity

As @ibenjelloun suggested the solution is better with an extra button going from first subquestion to the second subquestion because implementing a back button is only possible this way. 正如@ibenjelloun所建议的那样,从第一个子问题到第二个子问题有一个额外的按钮是更好的解决方案,因为只有通过这种方式才能实现back按钮。

Here is the final component that works: 这是有效的最终组件:

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);
  }

} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM