使用 Angular 的控件值访问器在自定义表单控件中验证未更新

问题描述 投票:0回答:1

我使用 Control Value Accessor 接口在 Angular 中构建了一个共享输入组件。输入本身可以工作,但当我更改输入中的值时,验证不会更新。这是我创建的输入组件。

import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
  AbstractControl,
  ControlValueAccessor,
  FormsModule,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ValidationErrors,
  Validator,
} from '@angular/forms';

@Component({
  selector: 'app-input',
  standalone: true,
  imports: [FormsModule],
  templateUrl: './input.component.html',
  styleUrl: './input.component.scss',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputComponent),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => InputComponent),
      multi: true,
    },
  ],
})
export class InputComponent implements ControlValueAccessor, Validator {
  @Input() value: string = '';
  @Input() type: string = 'text';
  @Input() placeholder: string = '';
  @Input() disabled: boolean = false;
  @Input() label: string = '';
  // default to a random id unless one is provided.
  @Input() id: string = '';

  isInvalid: boolean = false;
  touched: boolean = false;

  onChange = (value: string) => {};
  onTouched = () => {};
  onValidationChange = () => {};
  errorMessage: string = '';

  writeValue(value: string): void {
    this.value = value;
  }

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

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

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  onInputChange($event: Event) {
    const input = $event.target as HTMLInputElement;
    this.value = input.value;
    this.onChange(this.value);
    this.markAsTouched();
    this.onValidationChange();
  }

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

  validate(control: AbstractControl): ValidationErrors | null {
    this.errorMessage = this.getErrorMessage(control.errors);
    return control.errors;
  }

  registerOnValidatorChange(onValidationChange: () => void): void {
    this.onValidationChange = onValidationChange;
  }

  private getErrorMessage(errors: ValidationErrors | null): string {
    if (!this.touched || !errors) {
      return '';
    }
    if (errors['required']) {
      return 'This field is required';
    }
    if (errors['minLength']) {
      return `Minimum length is ${errors['minLength'].requiredLength}.`;
    }

    return '';
  }
}

当我在验证方法中记录control.errors时,它仍然显示所需的验证仍然有错误。

我有一个示例角度项目在这里显示这个问题:

https://stackblitz.com/edit/stackblitz-starters-fy4zue?file=src%2Finput%2Finput.component.ts

我尝试过的事情:

  • 我将 [(ngModel)] 两种方式绑定和 (ngModelChanges) 侦听器更改为简单的 [值] 和(输入)侦听器,但这没有帮助。
  • 我注册了 onValidationChange 方法,但不起作用。

我做错了什么,或者我忘记添加什么,或者这是一个实际的角度错误?

javascript angular angular-reactive-forms angular-forms controlvalueaccessor
1个回答
0
投票

此场景不需要验证器,当您想在自定义组件中嵌入验证时可以使用它。

对于您的场景,您所需要做的就是调用

ngOnInit
onChange
上的函数,并确保使用以下方法获取
formControl
对象。

constructor(
  private controlContainer: ControlContainer,
  private elementRef: ElementRef
) {}

ngOnInit() {
  const formControlName =
    this.elementRef.nativeElement.getAttribute('formcontrolname');
  console.log(this.controlContainer, formControlName);
  this.control = this.controlContainer?.control?.get(formControlName) || null;
  console.log(this.control);
  this.errorMessage = this.getErrorMessage(this.control.errors);
}

然后在Change时,我们再次调用

getErrorMessage

onInputChange($event: Event) {
  const input = $event.target as HTMLInputElement;
  this.value = input.value;
  console.log('asdf', this.value);

  this.onChange(this.value);
  this.errorMessage = this.getErrorMessage(this.control.errors);
  this.markAsTouched();
  // this.onValidationChange();
}

完整代码:

import {
  Component,
  ElementRef,
  forwardRef,
  Host,
  inject,
  Input,
  OnInit,
  Optional,
  SkipSelf,
} from '@angular/core';
import {
  AbstractControl,
  ControlContainer,
  ControlValueAccessor,
  FormsModule,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ValidationErrors,
  Validator,
  FormControl,
  NgControl,
} from '@angular/forms';

@Component({
  selector: 'app-input',
  standalone: true,
  imports: [FormsModule],
  templateUrl: './input.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputComponent),
      multi: true,
    },
  ],
})
export class InputComponent implements ControlValueAccessor {
  value: string = '';
  @Input() type: string = 'text';
  @Input() placeholder: string = '';
  @Input() disabled: boolean = false;
  @Input() label: string = '';
  // default to a random id unless one is provided.
  @Input() id: string = '';

  isInvalid: boolean = false;
  touched: boolean = false;

  onChange = (value: string) => {};
  onTouched = () => {};
  errorMessage: string = '';
  control!: any;

  constructor(
    private controlContainer: ControlContainer,
    private elementRef: ElementRef
  ) {}

  ngOnInit() {
    const formControlName =
      this.elementRef.nativeElement.getAttribute('formcontrolname');
    console.log(this.controlContainer, formControlName);
    this.control = this.controlContainer?.control?.get(formControlName) || null;
    console.log(this.control);
    this.errorMessage = this.getErrorMessage(this.control.errors);
  }

  writeValue(value: string): void {
    console.log(value);
    this.value = value;
  }

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

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

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  onInputChange($event: Event) {
    const input = $event.target as HTMLInputElement;
    this.value = input.value;
    console.log('asdf', this.value);

    this.onChange(this.value);
    this.errorMessage = this.getErrorMessage(this.control.errors);
    this.markAsTouched();
    // this.onValidationChange();
  }

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

  private getErrorMessage(errors: ValidationErrors | null): string {
    if (errors?.['required']) {
      return 'This field is required';
    }
    if (errors?.['minLength']) {
      return `Minimum length is ${errors['minLength'].requiredLength}.`;
    }
    return '';
  }
}

Stackblitz 演示

© www.soinside.com 2019 - 2024. All rights reserved.