在Angular Formly中,使用自定义组件作为字段类型时,经常会发现验证错误信息没有在输入框失焦(blur)时立刻显示,而是直到提交或再次变更才出现。这会影响用户体验,也让表单校验看起来不可靠。本文分析产生该问题的原因,并给出可落地的解决方案。

问题产生的原因
Formly依赖Angular的AbstractControl状态来判断是否显示验证消息。对于原生控件,Angular会自动在blur时标记控件为touched。但自定义组件若未正确实现ControlValueAccessor的onTouched方法,Formly无法感知失焦动作,因此不会切换显示验证消息。
常见遗漏点
- 自定义组件未注入并调用onTouched回调
- formly字段配置缺少validation或messages定义
- 未使用formly提供的FormlyFieldConfig来关联状态
解决方案
第一步:自定义组件实现onTouched
在自定义组件中,除了writeValue和registerOnChange,还必须实现registerOnTouched,并在宿主元素blur时调用它。
import <Component> from '@angular/core';
import <ControlValueAccessor, NG_VALUE_ACCESSOR> from '@angular/forms';
@Component({
selector: 'app-custom-input',
template: `<input (blur)="onBlur()" (input)="onInput($event)" [value]="value">`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: CustomInputComponent, multi: true }
]
})
export class CustomInputComponent implements ControlValueAccessor {
value: string = '';
onChange: (val: string) => void = () => {};
onTouched: () => void = () => {};
writeValue(val: string): void {
this.value = val;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onInput(event: any): void {
this.value = event.target.value;
this.onChange(this.value);
}
onBlur(): void {
// 关键:通知Formly控件已被触碰
this.onTouched();
}
}
第二步:配置Formly字段与验证消息
在formly配置中,需要为字段声明验证规则,并通过validation.messages定义对应文本。同时可借助expressionProperties控制显示时机。
import <FormlyFieldConfig> from '@ngx-formly/core';
const fields: FormlyFieldConfig[] = [
{
key: 'username',
type: 'custom-input',
templateOptions: {
label: '用户名',
required: true
},
validation: {
messages: {
required: '用户名不能为空'
}
}
}
];
第三步:模板中启用错误展示
在表单模板里,通过formly提供的formly-field组件渲染,并确保外层form使用带updateOn或默认blur触发校验的设定。
<form [formGroup]="form"> <formly-form [form]="form" [fields]="fields" [model]="model"></formly-form> </form>
小结
Angular Formly自定义组件验证消息未在失焦时显示,核心在于自定义组件没有把blur状态同步给表单控件。只要正确实现registerOnTouched并在blur时调用,配合Formly的validation配置,就能让错误提示在失焦后正常出现。建议封装基础自定义组件时统一处理onTouched,避免重复踩坑。
Angular_Formly自定义组件验证消息修改时间:2026-07-30 04:24:19