Ручная проверка угловой реактивной формы дает ExpressionChangedAfterItHasBeenCheckedError - PullRequest
0 голосов
/ 02 мая 2018

Я использую Angular 5.0.0 и материал 5.2.2.

Эта форма содержит два подвопроса. Пользователь подает заявку два раза в одной форме. Это должно продолжаться, потому что я представляю здесь очень упрощенную версию моей оригинальной формы.

После первой отправки во втором подвопросе я проверяю, установлен ли хотя бы один флажок. Если нет, я делаю this.choicesSecond.setErrors({'incorrect': true});. это правильно отключает кнопку submit. Но это дает ошибку:

`ExpressionChangedAfterItHasBeenCheckedError: выражение изменилось после того как это было проверено. Предыдущее значение: «true». Текущее значение: «ложь».

Я думаю, что это связано с 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);
  }

}

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Решение @ibenjelloun хорошо работает с двумя настройками (см. Также комментарии под его решением):

  1. if (c.value.length >= min) необходимо if (c.value.filter(item => item).length >= min) фильтровать только отмеченные флажки.

  2. setControl для 'choicesSecond' должен быть выполнен после первой отправки в методе 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);
  }

} 
0 голосов
/ 02 мая 2018

Проблемы возникают из-за this.choicesSecond.setErrors({'incorrect': true});, когда вы нажимаете кнопку Отправить, вы создаете компонент и в то же время изменяете его значение. Это не работает в режиме разработки из-за дополнительной проверки, выполняемой angular. Вот хорошая статья об этой ошибке.

Для проверки формы вы можете использовать пользовательский валидатор, пример в этом посте :

minLengthArray(min: number) {
    return (c: AbstractControl): {[key: string]: any} => {
        if (c.value.length >= min)
            return null;

        return { 'minLengthArray': {valid: false }};
    }
}

А для шагов, так как вы используете угловой материал, вы можете использовать mat-stepper .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...