Angular表单验证:如何在同一对象中传递表单字段值?
解决表单验证器中"Cannot read property 'controls' of undefined"的问题
嘿,这个问题我之前也碰到过!你报错的核心原因很简单:当你在创建this.myForm的代码里试图访问this.myForm.controls.Password.value时,this.myForm还没被赋值呢——这时候它还是undefined,自然找不到controls属性。
下面给你两种靠谱的实现方式,推荐第一种:
方式一:用验证器工厂函数(最推荐)
这种方式让你的自定义验证器变成一个"工厂",接收要匹配的字段名,然后返回真正的验证函数。这样在验证触发时,它会从控件的父表单里获取最新的字段值,完全避免依赖未初始化的表单实例。
首先修改你的自定义验证器:
export class CustomValidators { // 工厂函数,接收要匹配的字段名 static expectedMatchingFields(matchFieldName: string): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { // 从父控件(也就是表单组)中获取要匹配的字段 const matchControl = control.parent?.get(matchFieldName); // 如果找不到目标字段,直接返回null(不触发验证错误) if (!matchControl) return null; // 对比当前控件值和目标字段值,不匹配就返回错误对象 return control.value === matchControl.value ? null : { fieldsNotMatch: true }; }; } }
然后创建表单的时候这样用:
this.myForm = this.fb.group({ Id: [null], Password: ['', [Validators.required]], PasswordConfirm: ['', [ Validators.required, // 直接传要匹配的字段名"Password"即可 CustomValidators.expectedMatchingFields('Password') ]] });
这种方式的优势在于:不管Password的值怎么变化,每次验证都会取最新的数值,而且完全不会有初始化顺序的问题。
方式二:先创建表单,再动态添加验证器
如果你不想修改自定义验证器的结构,可以先创建基础表单,等this.myForm初始化完成后,再给PasswordConfirm控件添加验证器:
// 第一步:先创建不带自定义验证器的表单 this.myForm = this.fb.group({ Id: [null], Password: ['', [Validators.required]], PasswordConfirm: ['', [Validators.required]] }); // 第二步:表单初始化完成后,添加自定义验证器 const passwordControl = this.myForm.get('Password'); const passwordConfirmControl = this.myForm.get('PasswordConfirm'); if (passwordControl && passwordConfirmControl) { passwordConfirmControl.addValidators( CustomValidators.expectedMatchingFields(passwordControl.value) ); // 记得更新控件的验证状态 passwordConfirmControl.updateValueAndValidity(); }
⚠️ 注意:这种方式有个局限性——它只会用Password的初始值来做验证,如果用户后续修改了Password,验证器不会自动更新这个值。所以除非你的场景特殊,否则更推荐第一种方式。
另外补充个小细节:Angular 12+已经支持直接把验证器放在数组里,不用再写Validators.compose()啦,简化代码更清爽~
内容的提问来源于stack exchange,提问作者Andrew Howard
相关产品推荐
相关产品推荐

