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

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>

问题原因

  1. 验证器使用场景错误:你的matchpassword是表单级验证器(接收整个表单的AbstractControl),但你错误地将它添加到了password和confirm_password单个字段的验证器数组中。单个字段的验证器只能获取当前字段的控件,无法拿到另一个密码字段,导致这两处的验证逻辑完全不生效。
  2. 错误信息触发条件不严谨:当前仅判断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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:30:54