【问题标题】:How to properly implement nested forms with Validator and Control Value Accessor?如何使用 Validator 和 Control Value Accessor 正确实现嵌套表单?
【发布时间】:2019-01-30 03:36:35
【问题描述】:

在我的应用程序中,我需要一个可重用的嵌套表单组件,例如 Address。我希望我的AddressComponent 处理它自己的FormGroup,这样我就不需要从外部传递它。 在 Angular 会议 (video, presentation) 上,Angular Core 团队的成员 Kara Erikson 建议为嵌套表单实现 ControlValueAccessor,使嵌套表单实际上只是一个 FormControl

我也想通了,我需要实现Validator,这样我的嵌套表单的有效性就可以被主表单看到了。

最后我创建了嵌套表单需要扩展的SubForm类:

export abstract class SubForm implements ControlValueAccessor, Validator {

  form: FormGroup;

  public onTouched(): void {
  }

  public writeValue(value: any): void {
    if (value) {
      this.form.patchValue(value, {emitEvent: false});
      this.onTouched();
    }
  }

  public registerOnChange(fn: (x: any) => void): void {
    this.form.valueChanges.subscribe(fn);
  }

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

  setDisabledState(isDisabled: boolean): void {
    isDisabled ? this.form.disable()
      : this.form.enable();
  }

  validate(c: AbstractControl): ValidationErrors | null {
    return this.form.valid ? null : {subformerror: 'Problems in subform!'};
  }

  registerOnValidatorChange(fn: () => void): void {
    this.form.statusChanges.subscribe(fn);
  }
}

如果你想让你的组件用作嵌套表单,你需要做以下事情:

@Component({
  selector: 'app-address',
  templateUrl: './address.component.html',
  styleUrls: ['./address.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => AddressComponent),
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => AddressComponent),
      multi: true
    }
  ],
})

export class AddressComponent extends SubForm {

  constructor(private fb: FormBuilder) {
    super();
    this.form = this.fb.group({
      street: this.fb.control('', Validators.required),
      city: this.fb.control('', Validators.required)
    });
  }

}

除非我从主表单的模板中检查子表单的有效性状态,否则一切正常。在这种情况下会产生ExpressionChangedAfterItHasBeenCheckedError,请参见ngIf 语句(stackblitz code):

<form action=""
      [formGroup]="form"
      class="main-form">
  <h4>Upper form</h4>
  <label>First name</label>
  <input type="text"
         formControlName="firstName">
         <div *ngIf="form.controls['address'].valid">Hi</div> 
  <app-address formControlName="address"></app-address>
  <p>Form:</p>
  <pre>{{form.value|json}}</pre>
  <p>Validity</p>
  <pre>{{form.valid|json}}</pre>


</form>

【问题讨论】:

    标签: javascript html angular typescript angular-reactive-forms


    【解决方案1】:

    使用 ChangeDetectorRef

    检查此视图及其子视图。与分离结合使用 实施本地变更检测检查。

    这是为防止不一致而设置的警示机制 在模型数据和 UI 之间,以便不显示错误或旧数据 给页面上的用户

    参考:https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

    参考:https://angular.io/api/core/ChangeDetectorRef

    import { Component, OnInit,ChangeDetectorRef } from '@angular/core';
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    
    @Component({
      selector: 'app-upper',
      templateUrl: './upper.component.html',
      styleUrls: ['./upper.component.css']
    })
    export class UpperComponent implements OnInit {
    
      form: FormGroup;
    
      constructor(private fb: FormBuilder,private cdr:ChangeDetectorRef) {
        this.form = this.fb.group({
          firstName: this.fb.control('', Validators.required),
          address: this.fb.control('')
        });
      }
    
      ngOnInit() {
        this.cdr.detectChanges();
      }
    
    
    }
    

    您的分叉示例:https://stackblitz.com/edit/github-3q4znr

    【讨论】:

    • 谢谢!这是否意味着官方推荐的表单编写方式不起作用,因为它依赖于手动更改检测调用?
    • 它将起作用。在您的示例中,一些变化检测是如何在变化检测周期之后触发的,这就是角度抛出错误的原因
    • 我看到它有效,我的问题是它是否是角度的错误?
    • 是的,我知道这种错误以及如何处理它。令我惊讶的是,我们需要手动触发更改检测或人为地使验证器异步进行表单分解等基本操作。
    • 许多开发人员甚至将其视为一个错误。但肯定不是。这是一种警告机制,用于防止模型数据和 UI 之间的不一致,从而不会在页面上向用户显示错误或旧数据。也检查一下blog.angularindepth.com/…
    【解决方案2】:

    WriteValue 将在与正常变更检测生命周期挂钩的同一摘要周期中触发。

    要在不使用 changeDetectionRef 的情况下解决此问题,您可以定义有效性状态字段并进行响应式更改。

    public firstNameValid = false;

       this.form.controls.firstName.statusChanges.subscribe(
          status => this.firstNameValid = status === 'VALID'
        );
    

    &lt;div *ngIf="firstNameValid"&gt;Hi&lt;/div&gt;

    【讨论】:

      【解决方案3】:

      尝试在 *ngIf 的立场中使用 [hidden],它可以在没有 ChangeDetectorRef 的情况下工作。

      更新网址:https://stackblitz.com/edit/github-3q4znr-ivtrmz?file=src/app/upper/upper.component.html

      <div [hidden]="!form.controls['address'].valid">Hi</div>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-07
        • 2023-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多