在自定义反应式表单控件中持续更新验证状态?

Consistent updating of validation state in a custom reactive form control?

我正在尝试使用 outlined in this tutorial.

方法创建 material username 反应式表单控件

最终结果应该以这样的形式工作(fs-username-form 是自定义反应式表单组件):

<mat-card class="UsernameFormTestCard">
  <form class="UsernameForm" [formGroup]="form" (ngSubmit)="submit()">
    <mat-form-field>
      <input
        matInput
        placeholder="First name"
        type="text"
        formControlName="firstName"
      />
      <mat-hint *ngIf="!username">Example Monica</mat-hint>
      <mat-error>Please enter your first name</mat-error>
    </mat-form-field>

    <fs-username-form formControlName="username"></fs-username-form>

    <button mat-button type="submit" [disabled]="!form.valid">Submit</button>
  </form>
</mat-card>

如果表单有效,则 Submit 按钮启用。

它的工作原理......有点......它能够反应......但不是始终如一。最小字符数设置为 4。提交按钮在长度为 4 时启用。但是,如果我们开始从 username 中删除字符,则 submit 按钮只会在 username 的长度为 2.

后禁用

This is the Stackblitz demo showing this.

username-form.component.ts是这样实现的:

import { Component } from '@angular/core';
import {
  AbstractControl,
  ControlValueAccessor,
  FormControl,
  FormGroup,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ValidationErrors,
  Validator,
  Validators,
} from '@angular/forms';

@Component({
  selector: 'fs-username-form',
  templateUrl: './username-form.component.html',
  styleUrls: ['./username-form.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: UsernameFormComponent,
    },
    {
      provide: NG_VALIDATORS,
      multi: true,
      useExisting: UsernameFormComponent,
    },
  ],
})
export class UsernameFormComponent implements ControlValueAccessor, Validator {
  //=============================================
  // ControlValueAccessor API Methods
  //=============================================
  disabled: boolean = false;
  // Dummy initialization.
  //The implementation is passed in
  // with registerOnChange.
  onChange = (username: string) => {};
  onTouched = () => {};
  touched = false;
  usernameValue = '';

  writeValue(username: any): void {
    this.usernameValue = username;
  }

  //=============================================
  // Registration API Methods
  //=============================================

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

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

  markAsTouched() {
    if (!this.touched) {
      this.onTouched();
      this.touched = true;
    }
  }

  setDisabledState(disabled: boolean) {
    this.disabled = disabled;
    if (disabled) {
      this.usernameControl?.disable();
    } else {
      this.usernameControl?.enable();
    }
  }

  //=============================================
  // Validator API Methods
  //=============================================
  validate(control: AbstractControl): ValidationErrors | null {
    console.log('VALIDATE IS GETTING CALLED');
    console.log('Is the form valid: ', this.usernameForm.valid);

    if (this.usernameForm.valid) {
      return null;
    }

    let errors: any = {};

    errors = this.addControlErrors(errors, 'username');

    return errors;
  }

  addControlErrors(allErrors: any, controlName: string) {
    const errors = { ...allErrors };
    const controlErrors = this.usernameForm.controls[controlName].errors;

    if (controlErrors) {
      errors[controlName] = controlErrors;
    }
    return errors;
  }

 
  /**
   * Registers a call to onChange to inform
   * parent forms of valueChanges events.
   */
  constructor() {
    this.usernameControl?.valueChanges.subscribe((u) => {
      this.onChange(u);
    });
  }

  public usernameForm: FormGroup = new FormGroup({
    username: new FormControl('', [
      Validators.required,
      Validators.minLength(4),
    ]),
  });

  get username() {
    return this.usernameForm.get('username')?.value;
  }

  get usernameControl() {
    return this.usernameForm.get('username');
  }
}

username-form.component.html 模板如下所示:

<form class="UsernameForm" [formGroup]="usernameForm">
  <mat-form-field>
    <input
      matInput
      placeholder="username"
      type="text"
      formControlName="username"
    />
    <mat-hint *ngIf="!username">Example Monica</mat-hint>
    <mat-error>Please enter your username</mat-error>
  </mat-form-field>
</form>

可以看出它实现了 ControlValueAccessorValidator 接口。

当用户在 username 字段中键入内容时,会调用 onChange,如果我理解正确,这又会导致父表单在 [=13= 上调用验证] 控制。

为什么演示表单中的 submit 按钮没有持续更新或者为什么 username-form 组件没有更新?

成功了。我没有在 validate 方法中检查包含控件的表单是否有效,而是将其切换为检查控件本身是否有效,如下所示:

  //=============================================
  // Validator API Methods
  //=============================================
  validate(control: AbstractControl): ValidationErrors | null {

    if (this.usernameControl.valid) {
      return null;
    }

    let errors: any = {};

    errors = this.addControlErrors(errors, 'username');

    return errors;
  }

现在可以了。 This is a new working demo.