如何使用Validator和Control Value Accessor正确实现嵌套表单?

时间:2018-08-23 17:20:43

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

在我的应用程序中,我需要一个可重用的嵌套表单组件,例如 Address 。我希望我的AddressComponent处理它自己的FormGroup,以便不需要从外部传递它。 在Angular会议(videopresentation)上,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>

3 个答案:

答案 0 :(得分:3)

使用ChangeDetectorRef

  

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

     
    

这是一种预防机制,可防止出现不一致情况     在模型数据和用户界面之间,这样就不会显示错误或旧数据     给页面上的用户

  

参考: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

答案 1 :(得分:2)

WriteValue将在与正常变化检测lyfe周期挂钩相同的摘要周期中触发。

要解决此问题,而无需使用changeDetectionRef,则可以定义有效性状态字段并进行反应性更改。

public firstNameValid = false;

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

<div *ngIf="firstNameValid">Hi</div>

答案 2 :(得分:-1)

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

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

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