繁体   English   中英

动态嵌套反应形式:ExpressionChangedAfterItHasBeenCheckedError

[英]Dynamic nested reactive form: ExpressionChangedAfterItHasBeenCheckedError

我的反应形式是三个组件级别的深度。 父组件创建一个没有任何字段的新表单并将其传递给子组件。

起初,外部形式是有效的。 稍后,子组件会添加带有验证器(失败)的新表单元素,从而使外部表单无效。

我在控制台中收到ExpressionChangedAfterItHasBeenCheckedError错误。 我想修复那个错误。

不知何故,这只发生在我添加第三级嵌套时。 同样的方法似乎适用于两层嵌套。

Plunker: https ://plnkr.co/edit/GymI5CqSACFEvhhz55l1?p=preview

父组件

@Component({
  selector: 'app-root',
  template: `
    myForm.valid: <b>{{myForm.valid}}</b>
    <form>
      <app-subform [myForm]="myForm"></app-subform>
    </form>
  `
})
export class AppComponent implements OnInit {
  ...

  ngOnInit() {
    this.myForm = this.formBuilder.group({});
  }
}

子组件

@Component({
  selector: 'app-subform',
  template: `
    <app-address-form *ngFor="let addressData of addressesData;"
      [addressesForm]="addressesForm">
    </app-address-form>
  `
})
export class SubformComponent implements OnInit {
  ...

  addressesData = [...];

  constructor() { }

  ngOnInit() {
    this.addressesForm = new FormArray([]);
    this.myForm.addControl('addresses', this.addressesForm);
  }

子组件

@Component({
  selector: 'app-address-form',
  template: `
    <input [formControl]="addressForm.controls.addressLine1">
    <input [formControl]="addressForm.controls.city">
  `
})
export class AddressFormComponent implements OnInit {
  ...

  ngOnInit() {
    this.addressForm = this.formBuilder.group({
      addressLine1: [
        this.addressData.addressLine1,
        [ Validators.required ]
      ],
      city: [
        this.addressData.city
      ]
    });

    this.addressesForm.push(this.addressForm);
  }
}

在此处输入图像描述

要了解问题,您需要阅读有关ExpressionChangedAfterItHasBeenCheckedError错误文章您需要了解的所有内容。

对于您的特定情况,问题是您正在AppComponent中创建一个表单并在 DOM 中使用{{myForm.valid}}插值。 这意味着 Angular 将为更新 DOM 的AppComponent 运行 create 和 run updateRenderer 函数 然后使用子组件的ngOnInit生命周期钩子将带有控件的子组添加到此表单:

export class AddressFormComponent implements OnInit {
  @Input() addressesForm;
  @Input() addressData;

  ngOnInit() {
    this.addressForm = this.formBuilder.group({
      addressLine1: [
        this.addressData.addressLine1,
        [ Validators.required ]   <-----------
      ]

    this.addressesForm.push(this.addressForm); <--------

由于您未提供初始值并且您指定了必需的验证器,因此控件变得无效。 因此整个表单变得无效,表达式{{myForm.valid}}的计算结果为false 但是当 Angular 为AppComponent运行变更检测时,它的评估结果为true 这就是错误所说的。

一种可能的解决方法是在开始时将表单标记为无效,因为您计划添加所需的验证器,但 Angular 似乎没有提供这种方法。 您最好的选择可能是异步添加控件。 事实上,这就是 Angular 在源代码中所做的事情:

const resolvedPromise = Promise.resolve(null);

export class NgForm extends ControlContainer implements Form {
  ...

  addControl(dir: NgModel): void {
    // adds controls asynchronously using Promise
    resolvedPromise.then(() => {
      const container = this._findContainer(dir.path);
      dir._control = <FormControl>container.registerControl(dir.name, dir.control);
      setUpControl(dir.control, dir);
      dir.control.updateValueAndValidity({emitEvent: false});
    });
  }

因此,对于您而言,它将是:

const resolvedPromise = Promise.resolve(null);

@Component({
   ...
export class AddressFormComponent implements OnInit {
  @Input() addressesForm;
  @Input() addressData;

  addressForm;

  ngOnInit() {
    this.addressForm = this.formBuilder.group({
      addressLine1: [
        this.addressData.addressLine1,
        [ Validators.required ]
      ],
      city: [
        this.addressData.city
      ]
    });

    resolvedPromise.then(() => {
       this.addressesForm.push(this.addressForm); <-------
    })
  }
}

或者在AppComponent中使用一些变量来保存表单状态并在模板中使用它:

{{formIsValid}}

export class AppComponent implements OnInit {
  myForm: FormGroup;
  formIsValid = false;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.formBuilder.group({});
    this.myForm.statusChanges((status)=>{
       formIsValid = status;
    })
  }
}
import {ChangeDetectorRef} from '@angular/core';
....

export class SomeComponent {

  form: FormGroup;

  constructor(private fb: FormBuilder,
              private ref: ChangeDetectorRef) {
    this.form = this.fb.group({
      myArray: this.fb.array([])
    });
  }

  get myArray(): FormArray {
    return this.form.controls.myArray as FormArray;
  }

  addGroup(): void {
    const newGroup = this.fb.group({
      prop1: [''],
      prop2: ['']
    });

    this.myArray.push(newGroup);
    this.ref.detectChanges();
  }
}

我在 Angular 9 及更高版本的解决方案中遇到了相同的场景和相同的问题。 我稍微调整了一下:同步添加没有验证器的控件并异步添加所需的验证器......因为我需要立即控制,否则我得到一个错误cannot find formControl ...

这是我基于上面接受的答案的解决方案:

const resolvedPromise = Promise.resolve(null);
@Component({
  selector: 'password-input',
  templateUrl: './password-input.component.html',
  styleUrls: ['./password-input.component.css']
})
export class PasswordInputComponent implements OnInit {
  @Input() parentFormGroup : FormGroup;
  @Input() controlName : string = 'password';
  @Input() placeholder : string = 'Password';
  @Input() label : string = null;

  ngOnInit(): void {
    this.parentFormGroup.addControl(this.controlName, new FormControl(null));
   
    resolvedPromise.then( () => {
      this.parentFormGroup.get(this.controlName).setValidators(Validators.required)
      this.parentFormGroup.get(this.controlName).updateValueAndValidity();
    });
  }

按照 Max Koretskyi 的解决方案,我们可以在ngOnInit上使用 async/await 模式。

这是一个例子:

@Component({
  selector: 'my-sub-component',
  templateUrl: 'my-sub-component.component.html',
})
export class MySubComponent implements OnInit {
  @Input()
  form: FormGroup;

  async ngOnInit() {
    // Avoid ExpressionChangedAfterItHasBeenCheckedError
    await Promise.resolve(); 

    this.form.removeControl('config');
    
    this.form.addControl('config', new FormControl("", [Validator.required]));       
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM