You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为实现Validator的自定义ControlValueAccessor输入组件传递验证器?

自定义手机号输入组件验证状态异常问题

我正在开发一个可全应用复用的自定义手机号输入组件,包含国家码选择器和手机号输入框。已经实现了ControlValueAccessor,能作为单个值在表单中使用,但外部传入的Validators无法更新验证状态,required错误始终存在。

因为使用Ionic组件库,其输入组件样式依赖关联表单控件的有效性,所以我通过内部FormControl管理组件值,尝试同步整体控件的有效性,但问题仍未解决。


phone-input.component.ts

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import parsePhoneNumber from 'libphonenumber-js';

@Component({
  selector: 'app-phone-input',
  templateUrl: './phone-input.component.html',
  styleUrls: ['./phone-input.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: PhoneInputComponent,
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: PhoneInputComponent,
      multi: true,
    },
  ],
})
export class PhoneInputComponent implements ControlValueAccessor, Validator {
  countryCode: FormControl = new FormControl('');
  nationalNumber: FormControl = new FormControl('');

  constructor() { }

  validate(control: FormControl) {
    const isValid = !!this.nationalNumber.value && !!this.countryCode.value;

    // control的状态、有效性、错误信息从未更新,除非返回新内容
    // 能看到异步验证器触发,但结果未关联到传入的FormControl参数
    // 即使已设置值,{ required: true }错误始终存在
    console.log(control);
    console.log(control.status);
    console.log(control.valid);
    console.log(control.errors);

    const allErrors = {
      ...control.errors,
      // ...{ invalid: !isValid },
    }

    console.log(allErrors)
    
    this.nationalNumber.setErrors(allErrors);
    return allErrors;
  }

  writeValue(value: string) {
    const phoneNumber = parsePhoneNumber(value);
    if (phoneNumber) {
      this.countryCode.setValue(phoneNumber.countryCallingCode);
      this.nationalNumber.setValue(phoneNumber.nationalNumber);
    }
  }

  onChange: any = () => { };

  onTouched: any = () => { };

  myChanged(event: Event) {
    this.onChange('+' + this.countryCode.value + this.nationalNumber.value);
  }

  myTouched(event: Event) {
    this.onTouched('+' + this.countryCode.value + this.nationalNumber.value);
  }

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

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

  setDisabledState(isDisabled: boolean): void {
    if (isDisabled) {
      this.countryCode.disable();
      this.nationalNumber.disable();
    } else {
      this.countryCode.enable();
      this.nationalNumber.enable();
    }
  }

  formatMap: { [country: string]: Format } = {
    'US': {
      countryCodeValue: "1",
      countryCodeLabel: "🇺🇸 +1",
      nationalNumberMask: "(000) 000-0000",   // TODO: 实现掩码
    },
  }

  get countryList() {
    return Object.keys(this.formatMap);
  }

}

interface Format {
  countryCodeValue: string;
  countryCodeLabel: string;
  nationalNumberMask: string;
}

phone-input.component.html

<div class="phone-input-container">
  <div class="country-code-input">
    <select [formControl]="countryCode" (change)="myChanged($event)">
      <option *ngFor="let country of countryList" [value]="formatMap[country].countryCodeValue">
        {{formatMap[country].countryCodeLabel}}
      </option>
    </select>
  </div>
  <div class="phone-number-input">
    <input type="number" [formControl]="nationalNumber" (input)="myChanged($event)" (blur)="myTouched($event)">
  </div>
</div>

login.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
  loginForm = new FormGroup({
    phone: new FormControl('', {
      validators: [ Validators.required ],
      asyncValidators: [ uniqueFieldValidator('phoneNumber') ],  
      updateOn: 'blur',
    }),
  });

  constructor() { }

  ngOnInit() { }

  onSubmit() {
    console.log(this.phone);
  }

  get phone() {
    return this.loginForm.get('phone') as FormControl;
  }

}

login.component.html

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <app-phone-input id="phone" formControlName="phone"></app-phone-input>
</form>

内容的提问来源于stack exchange,提问作者Galen Howlett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 16:47:45