Skip to content

[BUG]: Component implementing ControlValueAccessor used inside ngFor loses data #37920

@rafaelmfonseca

Description

@rafaelmfonseca

🐞 bug report

Affected Package

The issue is caused by package @angular/forms.
Angular 8+ (I haven't tested in versions before v8.0).

Is this a regression?

No, this bug happens since v8.0, and remains in v10.

Description

This bug occurs when you have a component that implements ControlValueAccessor and it's used inside an *ngFor.
Dependending on the order that you add and remove the elements, some data are lost.

import { Component, VERSION } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
  <form>
  <div *ngFor="let data of dataList; index as i;">
    <label for="inputDescription{{i}}">Description</label>
    <sample-input
      [(ngModel)]="data.description"
      [ngModelOptions]="{ standalone: true }"
      [index]="i"></sample-input>
    <button type="button" (click)="removeData(i)">Remove</button>
  </div>
  <div>
    <button type="button" (click)="addData()">Add</button>
  </div>
  <div>
    <pre>{{ dataList | json }}</pre>
  </div>
</form>
  `
})
export class AppComponent  {
  name = 'Angular ' + VERSION.major;

  dataList = [];

  addData() {
    this.dataList.push({ description: '' });
  }

  removeData(index) {
    this.dataList.splice(index, 1);
  }
}
import { Component, StaticProvider, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm, ControlContainer } from '@angular/forms';

export const SAMPLE_INPUT_VALUE_ACCESSOR: StaticProvider = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => SampleInputComponent),
  multi: true
};

@Component({
  selector: 'sample-input',
  template: `<input type="text" id="inputDescription{{ index }}" [(ngModel)]="value" name="inputDescription{{ index }}" />`,
  providers: [SAMPLE_INPUT_VALUE_ACCESSOR],
  viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class SampleInputComponent implements ControlValueAccessor {

  @Input() index;

  private onChange: (_: any) => { };
  private onTouched: () => { };
  private _value: any;

  public get value() {
    return this._value;
  }

  public set value(value: any) {
    this._value = value;
    this.onChange(value);
  }

  writeValue(obj: any): void {
    this._value = obj;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {

  }
}

🔬 Minimal Reproduction

1 - Insert 4 elements (with value):
image
2 - Then remove the first element:
image
3 - Insert new element:
image
Problem: value 4 is gone

Example: https://stackblitz.com/edit/angular-ivy-uqpssy

🔥 Exception or Error

None

🌍 Your Environment

Angular Version:

@angular/animations10.0.1
@angular/common10.0.1
@angular/compiler10.0.1
@angular/core10.0.1
@angular/forms10.0.1
@angular/platform-browser10.0.1
@angular/platform-browser-dynamic10.0.1
@angular/router10.0.1
rxjs6.5.5
tslib2.0.0
zone.js

Anything else relevant?
Chrome version: 83.0.4103.116 - 64 bits

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions