3
votes

I currently have an ion-input that is going through a *ngFor so it repeats multiple times. When I want to add a new record (to add a new ion-input row) to the *ngFor loop, there is an error for:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'model: abc'. Current value: 'model: '.

Here are my codes:

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

Here are some images for your reference:

Image of Original UI (Before clicking on add or remove ion-input rows):

Image of Original UI (Before clicking on add or remove ion-input rows)

Image of UI (After typing in 'abc' into the first ion-input then click on the add button:

Image of UI (After typing in 'abc' into the first ion-input then click on the add button)

Image of the error:

enter image description here

What I am trying to achieve is even though the user has typed in something in the ion-input fields, if they want to add more fields, the fields will just appear below the original fields. I've read other solutions but all of them subscribe to a service and the solutions are for that, so I don't think I can apply it to my case. Please help!

UPDATE: Managed to solve the problem by following @GaurangDhorda's answer.

Codes:

.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 (Added in new method)

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

3
Replace [(ngModel)] = "values[i].answer" with [(ngModel)] = "item.answer"Sergey
@Sergey The error is still there after changing it.Bryan Artic
when does error occur? on load?Sergey Rudenko
It's the same error like the one mentioned in the post.Bryan Artic

3 Answers

0
votes
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();
 }
2
votes

Warning for others: The answer that is checked as "correct" is just a workaround for a wrong approach that initially mixed template driven forms (ngModel) with reactive forms. (formControlName) It should not be considered as the solution.

1) You either go with template driven form or reactive driven forms. Mixing them, does not provide a real value and can be considered as "code smell" and leads to errors as in your case. When you want to have a two way binding I would suggest to use [(ngModel)] as you did in your first draft of your question. But then please remove the formControlName=taskDetail. (I would be wrong in any case to have the same taskDetail formControlName within the *ngFor)

2) With the solution you have now you are essentially doing two-way-binding manually yourself.

3) I have two examples that show your problem with template driven forms and reactive forms:

Template Driven We need to have a distinct name attribute on your input when we use ngModel

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

Reactive Forms Reactive forms come with a little more to write in the component class. There we setup a FormArray that holds the values:

    <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);
  }
  ...
}
-1
votes

The following error does not come in prod mode.

Or

Add your values code in ngAfterViewInit() life cycle mode.