Angular 16:密码匹配自定义验证器无法在密码字段显示错误信息
Angular 16响应式注册表单:密码不匹配错误信息不显示问题
我正在开发Angular 16的响应式注册表单,已编写自定义验证器matchpassword用于检查password和confirm_password字段值是否匹配。目前表单在密码不匹配时会标记为无效(提交按钮禁用),但密码字段对应的“密码不匹配”错误信息却无法显示,想排查问题原因。
现有代码展示
自定义验证器 (matchpassword.validator.ts)
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms"; export const matchpassword : ValidatorFn = (control: AbstractControl):ValidationErrors|null => { let password = control.get('password'); let confirm_password = control.get('confirm_password'); if (password && confirm_password && password?.value != confirm_password?.value) { return { password_match : true } } return null; }
组件TS代码 (registration.component.ts)
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { matchpassword } from "../../custom-validators/matchpassword.validator"; @Component({ selector: 'app-registration', templateUrl: './registration.component.html', styleUrls: ['./registration.component.scss'] }) export class RegistrationComponent implements OnInit { public registrationForm!: FormGroup; public isSuccess: Boolean = false; constructor(private formBuilder: FormBuilder) { } get form() { return this.registrationForm.controls } public registerUser() { if (this.registrationForm.status !== 'INVALID') { // 显示成功提示 this.isSuccess = true; // 重置表单 this.registrationForm.reset(); } else { return; } } ngOnInit() { this.registrationForm = this.formBuilder.group({ firstName: ['', [Validators.required, Validators.minLength(3)]], lastName: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(6), matchpassword]], confirm_password: ['', matchpassword], terms: ['', Validators.requiredTrue], }, { validators: matchpassword }); } }
组件HTML代码 (registration.component.html)
<div *ngIf="isSuccess" class="alert alert-success alert-dismissible fade show text-center"> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> 注册成功! </div> <form [formGroup]="registrationForm" (ngSubmit)="registerUser()" novalidate > <div class="form-element mb-2"> <label for="firstName" class="form-label"> 名字 </label> <input type="text" id="firstName" formControlName="firstName" class="form-control form-control-sm" /> <span class="invalid-feedback" *ngIf="form['firstName']?.touched && form['firstName'].errors?.['required']" >名字字段不能为空</span > <span class="invalid-feedback" *ngIf="form['firstName'].touched && form['firstName'].errors?.['minlength']" >名字长度至少为3个字符</span > </div> <div class="form-element mb-2"> <label for="lastName" class="form-label"> 姓氏 </label> <input type="text" id="lastName" formControlName="lastName" class="form-control form-control-sm" /> <span class="invalid-feedback" *ngIf="form['lastName'].touched && form['lastName'].errors?.['required']" >姓氏字段不能为空</span > <span class="invalid-feedback" *ngIf="form['lastName'].touched && form['lastName'].errors?.['minlength']" >姓氏长度至少为3个字符</span > </div> <div class="form-element mb-2"> <label for="email" class="form-label"> 邮箱地址 </label> <input type="email" id="email" formControlName="email" class="form-control form-control-sm" /> <span class="invalid-feedback" *ngIf="form['email'].touched && form['email'].errors?.['required']" >邮箱字段不能为空</span > <span class="invalid-feedback" *ngIf="form['email'].touched && form['email'].errors?.['email']" >请输入有效的邮箱地址</span > </div> <div class="form-element mb-2"> <label for="password" class="form-label"> 密码 </label> <input type="password" id="password" formControlName="password" class="form-control form-control-sm" /> <span class="invalid-feedback" *ngIf="form['password'].touched && form['password'].errors?.['required']" >密码字段不能为空</span > <span class="invalid-feedback" *ngIf="form['password'].touched && form['password'].errors?.['minlength']" >密码长度至少为6个字符</span > <span class="invalid-feedback" *ngIf="form['password'].touched && form['password'].errors?.['passwordcomplexity']" >密码复杂度不够</span > <span class="invalid-feedback" *ngIf="form['password'].touched && registrationForm.errors?.['password_match']" >密码不匹配</span > </div> <div class="form-element mb-2"> <label for="confirm_password" class="form-label"> 确认密码 </label> <input type="password" id="confirm_password" formControlName="confirm_password" class="form-control form-control-sm" /> </div> <div class="form-element mb-2"> <input type="checkbox" formControlName="terms" id="terms" class="me-1" /> <span class="text-terms text-muted" >我同意 <a href="#" class="text-success">服务条款</a></span > <span class="invalid-feedback terms" *ngIf="form['terms'].dirty && form['terms'].errors?.['required']" >你必须同意服务条款</span > </div> <div class="pt-2"> <button type="submit" class="btn btn-sm btn-success w-100" [disabled]="!registrationForm.valid" > 提交 </button> </div> </form>
问题原因
- 验证器使用场景错误:你的
matchpassword是表单级验证器(接收整个表单的AbstractControl),但你错误地将它添加到了password和confirm_password单个字段的验证器数组中。单个字段的验证器只能获取当前字段的控件,无法拿到另一个密码字段,导致这两处的验证逻辑完全不生效。 - 错误信息触发条件不严谨:当前仅判断
password字段的touched状态,若用户只修改了confirm_password,即使密码不匹配,错误信息也不会显示。
解决方案
步骤1:修正验证器的使用范围
移除单个字段上的matchpassword,仅保留表单全局验证器,同时给confirm_password添加必填验证:
ngOnInit() { this.registrationForm = this.formBuilder.group({ firstName: ['', [Validators.required, Validators.minLength(3)]], lastName: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(6)]], // 移除matchpassword confirm_password: ['', Validators.required], // 移除matchpassword,添加必填验证 terms: ['', Validators.requiredTrue], }, { validators: matchpassword }); }
步骤2:优化错误信息的触发条件
修改密码和确认密码字段的错误提示逻辑,只要其中一个字段被触碰过,就显示密码不匹配的错误:
<!-- 密码字段的错误信息 --> <span class="invalid-feedback" *ngIf="(form['password'].touched || form['confirm_password'].touched) && registrationForm.errors?.['password_match']" >密码不匹配</span > <!-- 确认密码字段添加错误提示 --> <div class="form-element mb-2"> <label for="confirm_password" class="form-label"> 确认密码 </label> <input type="password" id="confirm_password" formControlName="confirm_password" class="form-control form-control-sm" /> <span class="invalid-feedback" *ngIf="form['confirm_password'].touched && form['confirm_password'].errors?.['required']" >请确认密码</span > <span class="invalid-feedback" *ngIf="(form['password'].touched || form['confirm_password'].touched) && registrationForm.errors?.['password_match']" >密码不匹配</span > </div>
步骤3:优化验证器逻辑(可选)
添加判断,当两个密码字段都为空时不返回错误,避免页面初始加载就显示错误:
export const matchpassword : ValidatorFn = (control: AbstractControl):ValidationErrors|null => { let password = control.get('password'); let confirm_password = control.get('confirm_password'); // 两个字段都为空时,不返回错误 if (!password?.value && !confirm_password?.value) { return null; } if (password && confirm_password && password?.value !== confirm_password?.value) { return { password_match: true } } return null; }
内容的提问来源于stack exchange,提问作者Razvan Zamfir
相关产品推荐
相关产品推荐

