如何在 Ionic 4 中使用离子输入修复 *ngFor 的 ExpressionChangedAfterItHasBeenCheckedError

How to fix ExpressionChangedAfterItHasBeenCheckedError for *ngFor with ion-input in Ionic 4

我目前有一个通过 *ngFor 的离子输入,所以它会重复多次。当我想向 *ngFor 循环添加一条新记录(添加一个新的离子输入行)时,出现以下错误:

ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后发生了变化。先前值:'model: abc'。当前值:'model: '.

这是我的代码:

.html

<ion-item *ngFor="let item of values; let i = index;">
      <ion-input formControlName="taskDetail" [(ngModel)] = "values[i].answer" type="text"></ion-input>
      <ion-icon name="remove-circle" (click)="removeTaskOption(i)" slot="end"></ion-icon>
</ion-item>

.ts

checklistCounter = [];
values = [];

constructor() {
    this.checklistCounter = Array(this.numCounter).fill(1).map((x, i) => i + 1);
    for (let i = 0; i < this.checklistCounter.length; i++) {
      let data = {
        id: i,
        answer:''
      }
      this.values.push(data);
    }
}


addNewTaskOption(){
  let data = {
    id: (this.values[this.values.length - 1].id + 1),
    answer:''
  }
 this.values.push(data);
}

removeTaskOption(i:number){
  this.values.splice(i, 1);
}

这里有一些图片供您参考:

原图UI(在点击添加或删除离子输入行之前):

UI 的图像(在第一个离子输入中输入 'abc' 然后单击添加按钮:

错误图片:

我想要实现的是,即使用户在离子输入字段中输入了一些内容,如果他们想添加更多字段,这些字段也会出现在原始字段下方。我读过其他解决方案,但他们都订阅了一项服务,而解决方案是为此目的,所以我认为我不能将它应用到我的案例中。请帮忙!

更新:按照@GaurangDhorda 的回答设法解决了问题。

代码:

.html

<ion-item *ngFor="let item of values; let i = index;">
      <ion-input formControlName="taskDetail" [value]="item.answer" (change)="setQuantity($event.target.value, i)" type="text"></ion-input>
      <ion-icon name="remove-circle" (click)="removeTaskOption(i)" slot="end"></ion-icon>
</ion-item>

.ts(在新方法中添加)

setQuantity(ev:string, i:number){
     this.values[i].answer = ev;    
   }

Add thid piece of code to your compoent.ts file...


import { Component, Input, ChangeDetectionStrategy,ChangeDetectorRef } from '@angular/core';

 @Component({
   selector: 'component',
   templateUrl: 'component.html',
   changeDetection: ChangeDetectionStrategy.OnPush
 })

 constructor(private cdRef:ChangeDetectorRef){} 
 ngAfterViewInit() {
    this.cdRef.detectChanges();
 }

生产模式下不会出现以下错误。

在 ngAfterViewInit() 生命周期模式中添加您的值代码。

其他人警告: 检查为 "correct" 的答案只是一种错误方法的变通方法,该方法最初将模板驱动形式 (ngModel) 与反应形式混合在一起。 (formControlName)不应将其视为解决方案。

1) 您要么使用模板驱动形式,要么使用反应式驱动形式。混合它们,不提供真正的价值,可以被视为 "code smell" 并导致错误,如您的情况。 当您想进行双向绑定时,我建议您像在问题的初稿中那样使用 [(ngModel)] 。但是请删除 formControlName=taskDetail。 (在任何情况下,在 *ngFor 中使用相同的 taskDetail formControlName 都是错误的)

2) 使用您现在拥有的解决方案,您实际上是在手动进行双向绑定。

3) 我有两个例子可以说明 template driven forms and reactive forms:

的问题

模板驱动 当我们使用 ngModel

时,我们需要在您的 input 上有一个独特的 name 属性
    <form>
      <ion-item *ngFor="let item of values; let i = index;">
        <ion-input [(ngModel)]="item.answer" type="text" name="answer_{{i}}"></ion-input>
        <button (click)="removeTaskOption(i)">Remove</button>
      </ion-item>

      <button (click)="addNewTaskOption()">Add answer</button>
    </form>

反应形式 响应式表单在组件 class 中包含更多内容。我们在那里设置了一个 FormArray 来保存值:

    <form [formGroup]="myForm">

      <div formArrayName="taskDetail">
        <ion-item *ngFor="let item of taskDetail.controls; let i = index;">
          <ion-input [formControlName]="i" type="text"></ion-input>
          <button (click)="removeTaskOption(i)">Remove</button>
        </ion-item>
      </div>

      <button (click)="addNewTaskOption()">Add answer</button>

    </form>
export class AppComponent {

  myForm = new FormGroup({
    taskDetail: new FormArray([
      new FormControl('value 1'),
      new FormControl('value 2')
    ])
  });

  get taskDetail() {
    return this.myForm.get('taskDetail') as FormArray;
  }

  addNewTaskOption(){
    this.taskDetail.push(new FormControl('value NEWWW'));
  }

  removeTaskOption(i:number){
    this.taskDetail.removeAt(i);
  }
  ...
}