Угловой компонент в форме 12 гнезд в списке вызывает ошибку NG0100

#angular #angular-reactive-forms #angular-forms

Вопрос:

Версия : Угловой 12.2 Стекблитц : https://stackblitz.com/edit/angular-ivy-r8cpbh?file=src/app/app.component.html

Привет, у меня есть проект с 3 компонентами типа :

  • Родитель со списком детей
  • Ребенок со списком дочерних детей
  • Дочерний автономный компонент

На родительском уровне мы можем добавить ребенка

На уровне ребенка мы можем добавить дочерний элемент

Все работает нормально, но когда я добавляю новую строку, иногда (при изменении допустимого поля) у меня появляется это сообщение в FormGroup.недопустимо :

NG0100: ExpressionChangedAfterItHasBeenCheckedError: Выражение изменилось после проверки. Предыдущее значение для «отключено»: «ложь». Текущее значение: «истина»

Я знаю, что это распространенная ошибка, но на этот раз я не нашел, откуда берутся ошибки…

Пожалуйста, помогите.

 Parent
   |____Child
           |______SubChild
           |______SubChild
           |______SubChild
   |____Child
           |______SubChild
   |____Child
           |______SubChild
           |______SubChild

 

Родительские ts

  public data!: ParentData;
  public formGroup!: FormGroup;

  get children() {
    return <FormArray>this.formGroup.controls.children;
  }

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.data = {
      parentField1: 'string',
      parentField2: 'string',
      parentHiddenField1: 'string',
      children: []
    };
    this.formGroup = this.toFormGroup(this.data);

    this.formGroup.valueChanges.subscribe(value => console.log(value));
  }

  add() {
    this.children.push(new FormGroup({}));
    this.data.children.push({
      id: 1,
      childField1: 'string',
      subchildren: []
    });
  }

  private toFormGroup(data: ParentData): FormGroup {
    const formGroup = this.fb.group({
      parentField1: [data.parentField1, Validators.required],
      parentField2: [data.parentField2, Validators.required],
      parentHiddenField1: [data.parentHiddenField1],
      children: new FormArray([])
    });

    return formGroup;
  }

  submit(finalData: ParentData) {
    console.log(finalData);
  }
}

 

Parent Html

 <form [formGroup]="formGroup" (ngSubmit)="submit(formGroup.value)">
  <label for="parentField1">Parent Field 1</label>
  <input formControlName="parentField1" />

  <br />

  <label for="parentField2">Parent Field 2</label>
  <input formControlName="parentField2" />



  <div formArrayName="children">
    <button type="button" (click)="add()"> </button>
    <div *ngFor="let child of data.children; let i=index">
      <div>Parent</div>
      <app-test-list-child [formGroup]="children.controls[i] | formGroup" [data]="child">
      </app-test-list-child>

    </div>
  </div>

  <button type="submit" [disabled]="formGroup.invalid">Send</button>
</form>
 

Child ts

 export class TestListChildComponent implements OnInit, AfterViewInit {
  @Input('formGroup')
  public formGroup!: FormGroup;

  @Input('data')
  public data!: ChildData;

  get subchildren() {
    return <FormArray>this.formGroup.get('subchildren');
  }

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    this.formGroup.addControl(
      'childField1',
      new FormControl(this.data.childField1 || '', Validators.required)
    );
    this.formGroup.addControl('subchildren', new FormArray([]));
  }
  ngAfterViewInit() {}

  add() {
    this.subchildren.push(new FormGroup({}));
    this.data.subchildren.push({
      childField1: 'test',
      childHiddenField1: '',
      childField2: '',
      id: 1
    });
  }
}

 

Child html

 <div [formGroup]="formGroup">
  <input formControlName="childField1" />
  <button type="button" (click)="add()">  </button>

  <div formArrayName="subchildren">

    <div *ngFor="let child of subchildren.controls; let i = index">
      <div>Child</div>

      <app-test-child [formGroup]="child | formGroup" [data]="data.subchildren[i]">
      </app-test-child>
    </div>
  </div>
</div>
 

SubChild ts

 export class TestChildComponent implements OnInit, AfterViewInit {

  @Input('formGroup')
  public formGroup!: FormGroup;

  @Input('data')
  public data!: SubChildData;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.toFormGroup(this.data);
  }

  ngAfterViewInit() {

  }

  private toFormGroup(data: SubChildData) {

    this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
    this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));

  }
}

 

Дочерний html

 <!-- child-form.component.html -->
SubChild
<div [formGroup]="formGroup">
  <label for="childField1">Child Field 1</label>
  <input formControlName="childField1" />
  <br/>
  <label for="childField1">Child Field 2</label>
  <input formControlName="childField2" />
</div>
 

Модель

 export interface ParentData {
  parentField1: string;
  parentField2: string;
  parentHiddenField1: string;
  children: ChildData[];
}

export interface ChildData {
  id: number;
  childField1: string;
  subchildren: SubChildData[];
}

export interface SubChildData {
  id: number;
  childField1: string;
  childField2: string;
  childHiddenField1: string;
}

 

Труба

 
@Pipe({
  name: 'formGroup'
})
export class FormGroupPipe implements PipeTransform {
  transform(value: AbstractControl, ...args: unknown[]): FormGroup {
    return value as FormGroup;
  }
}

 

Ответ №1:

Добавление this.cdr.detectChanges() в конце add дочернего метода » » исправит это.

Комментарии:

1. Thx, но это добавляет новое сообщение об ошибке : NG0303: Не удается привязаться к «NgForOf», так как это неизвестное свойство «div».

2. Мой плохой, это была ошибка стекблитца, ваше изменение заставило его работать очень много, не могли бы вы объяснить мне, что делает this.cd.DetectChanges(); это не повлияло на значение и ценность обновления, пожалуйста ?

3. AFAIK, это не рекомендуемое решение. Подробнее здесь: подробно .dev/сообщения/1001/..

Ответ №2:

Ошибка возникает из-за того, что в test-child-component.ts у вас есть необходимый валидатор:

   private toFormGroup(data: SubChildData) {

    this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
    this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));

  }
 

…но в test-list-component.ts вы не назначаете значение для элемента управления childField2, и форма становится недействительной.

Самым простым решением было бы назначить значение по умолчанию для второго элемента управления:

   add() {
    this.subchildren.push(new FormGroup({}));
    this.data.subchildren.push({
      childField1: 'test',
      childHiddenField1: '',
      childField2: 'default value',
      id: 1
    });
  }
 

Комментарии:

1. Thx для вашего ответа, но эти поля должны быть «Пустыми» в начале.

Ответ №3:

В конце концов я обнаружил, что pb был своего рода асинхронным шаблоном …

Я добавляю параметр setTimeout для создания форм, и теперь все работает хорошо.

Ребенок

 setTimeout(() => {
 this.formGroup.addControl(
      'childField1',
      new FormControl(this.data.childField1 || '', Validators.required)
    );
    this.formGroup.addControl('subchildren', new FormArray([]));
});

 

Дочерний элемент

 setTimeout(() => {
    this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
    this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));
});