MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular表单验证的常见规则与应用

2023-04-022.5k 阅读

Angular 表单验证的常见规则与应用

1. 引言

在前端开发中,表单是用户与应用程序进行交互的重要方式。确保用户输入的数据符合特定的格式和要求,对于保证应用程序的稳定性和数据的准确性至关重要。Angular 提供了一套强大且灵活的表单验证机制,帮助开发者轻松实现各种验证规则。

2. Angular 表单类型

2.1 模板驱动表单

模板驱动表单是 Angular 中较为简单直观的表单创建方式。它依赖于模板中的指令来处理表单的状态和验证。例如,我们创建一个简单的登录表单:

<form #loginForm="ngForm">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" name="username" [(ngModel)]="user.username" required>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" name="password" [(ngModel)]="user.password" required minlength="6">
  </div>
  <button type="submit" [disabled]="!loginForm.form.valid">登录</button>
</form>

在上述代码中,#loginForm="ngForm" 表示这是一个模板驱动表单。requiredminlength 是 HTML5 原生的验证属性,Angular 会自动识别并应用这些验证规则。当用户输入不符合要求时,表单的 valid 属性会变为 false,从而禁用提交按钮。

2.2 响应式表单

响应式表单提供了更强大和灵活的方式来处理表单。它基于 FormGroupFormControlFormArray 等类来构建表单模型。以下是一个使用响应式表单实现同样登录功能的示例:

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

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  loginForm: FormGroup;

  constructor() {
    this.loginForm = new FormGroup({
      username: new FormControl('', Validators.required),
      password: new FormControl('', [Validators.required, Validators.minLength(6)])
    });
  }

  onSubmit() {
    if (this.loginForm.valid) {
      console.log(this.loginForm.value);
    }
  }
}
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" formControlName="username">
    <div *ngIf="loginForm.get('username').hasError('required') && (loginForm.get('username').touched || loginForm.get('username').dirty)">
      用户名不能为空
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" formControlName="password">
    <div *ngIf="loginForm.get('password').hasError('required') && (loginForm.get('password').touched || loginForm.get('password').dirty)">
      密码不能为空
    </div>
    <div *ngIf="loginForm.get('password').hasError('minlength') && (loginForm.get('password').touched || loginForm.get('password').dirty)">
      密码长度至少为 6 位
    </div>
  </div>
  <button type="submit" [disabled]="!loginForm.valid">登录</button>
</form>

在响应式表单中,我们通过 FormGroupFormControl 构建表单结构,并在创建 FormControl 时直接传入验证器。同时,在模板中通过 formControlName 绑定到对应的 FormControl,并根据 FormControl 的错误状态来显示相应的错误提示信息。

3. 常见验证规则

3.1 必需字段验证(Required)

无论是模板驱动表单还是响应式表单,required 都是最基本的验证规则。在模板驱动表单中,直接在 input 元素上添加 required 属性即可:

<input type="text" name="name" [(ngModel)]="person.name" required>

在响应式表单中,通过 Validators.required 来实现:

nameControl = new FormControl('', Validators.required);

当输入框为空时,该 FormControlhasError('required') 方法将返回 true

3.2 最小长度验证(MinLength)

用于限制输入内容的最小长度。在模板驱动表单中:

<input type="text" name="password" [(ngModel)]="user.password" minlength="6">

在响应式表单中:

passwordControl = new FormControl('', [Validators.required, Validators.minLength(6)]);

如果输入的密码长度小于 6 位,hasError('minlength') 将返回 true

3.3 最大长度验证(MaxLength)

与最小长度验证类似,用于限制输入内容的最大长度。模板驱动表单:

<input type="text" name="description" [(ngModel)]="product.description" maxlength="200">

响应式表单:

descriptionControl = new FormControl('', Validators.maxLength(200));

当输入内容超过 200 字符时,hasError('maxlength')true

3.4 模式匹配验证(Pattern)

可以使用正则表达式来验证输入内容是否符合特定模式。比如验证邮箱格式: 模板驱动表单:

<input type="email" name="email" [(ngModel)]="user.email" pattern="[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+">

响应式表单:

emailControl = new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+')]);

若输入的邮箱格式不正确,hasError('pattern')true

3.5 数字范围验证(Min 和 Max)

用于验证输入的数字是否在指定范围内。例如,验证年龄在 18 到 60 之间: 模板驱动表单:

<input type="number" name="age" [(ngModel)]="person.age" min="18" max="60">

响应式表单:

ageControl = new FormControl('', [Validators.required, Validators.min(18), Validators.max(60)]);

当年龄不在这个范围内时,相应的 hasError('min')hasError('max')true

4. 自定义验证规则

4.1 创建自定义验证器函数

有时候,Angular 提供的内置验证规则无法满足我们的需求,这时就需要创建自定义验证器。以验证密码强度为例,密码要求至少包含一个大写字母、一个小写字母和一个数字:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function passwordStrengthValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = control.value;
    if (!value) {
      return null;
    }

    const hasUpperCase = /[A-Z]/.test(value);
    const hasLowerCase = /[a-z]/.test(value);
    const hasDigit = /\d/.test(value);

    const isValid = hasUpperCase && hasLowerCase && hasDigit;
    return isValid? null : { passwordStrength: true };
  };
}

4.2 在响应式表单中使用自定义验证器

passwordControl = new FormControl('', [Validators.required, passwordStrengthValidator()]);

4.3 在模板驱动表单中使用自定义验证器

首先,我们需要创建一个指令来包装自定义验证器:

import { Directive, forwardRef } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';
import { passwordStrengthValidator } from './password-strength.validator';

@Directive({
  selector: '[appPasswordStrength]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PasswordStrengthDirective),
      multi: true
    }
  ]
})
export class PasswordStrengthDirective implements Validator {
  validate(control: AbstractControl): { [key: string]: any } | null {
    return passwordStrengthValidator()(control);
  }
}

然后在模板驱动表单中使用该指令:

<input type="password" name="password" [(ngModel)]="user.password" appPasswordStrength>

5. 表单验证状态与错误处理

5.1 表单验证状态

Angular 表单的每个 FormControl 都有以下几种验证状态:

  • valid:表示输入值通过了所有验证规则。
  • invalid:表示输入值未通过至少一个验证规则。
  • pristine:表示输入框未被用户修改过。
  • dirty:表示输入框已被用户修改过。
  • touched:表示输入框已被用户聚焦并失去焦点。
  • untouched:表示输入框未被用户聚焦过。

我们可以通过这些状态来控制表单的显示和行为。例如,在响应式表单中:

<input type="text" formControlName="username">
<div *ngIf="loginForm.get('username').hasError('required') && (loginForm.get('username').touched || loginForm.get('username').dirty)">
  用户名不能为空
</div>

上述代码中,只有当用户名输入框被用户触摸或修改且验证失败时,才显示错误提示信息。

5.2 错误处理与提示

在模板驱动表单中,Angular 会自动根据验证属性生成相应的错误类,如 ng - invalidng - touched 等。我们可以利用这些类来显示错误提示。例如:

.ng - invalid.ng - touched {
  border - color: red;
}

在响应式表单中,通过 hasError 方法来判断是否存在特定错误,并显示相应的错误提示。如前文登录表单示例中,根据 passwordControlhasError('required')hasError('minlength') 来显示密码不能为空和密码长度不足的错误提示。

6. 跨字段验证

6.1 验证密码与确认密码匹配

在注册表单中,经常需要验证用户输入的密码和确认密码是否一致。在响应式表单中,可以通过创建一个自定义的跨字段验证器来实现:

import { AbstractControl } from '@angular/forms';

export function passwordMatchValidator(control: AbstractControl): { [key: string]: any } | null {
  const password = control.get('password');
  const confirmPassword = control.get('confirmPassword');

  if (password && confirmPassword && password.value!== confirmPassword.value) {
    return { passwordMismatch: true };
  }
  return null;
}

然后在构建 FormGroup 时使用该验证器:

registerForm = new FormGroup({
  password: new FormControl('', Validators.required),
  confirmPassword: new FormControl('', Validators.required)
}, { validators: passwordMatchValidator });

在模板中显示错误提示:

<input type="password" formControlName="password">
<input type="password" formControlName="confirmPassword">
<div *ngIf="registerForm.hasError('passwordMismatch') && (registerForm.get('confirmPassword').touched || registerForm.get('confirmPassword').dirty)">
  两次输入的密码不一致
</div>

6.2 其他跨字段验证场景

类似地,还可以实现如验证起始日期小于结束日期等跨字段验证逻辑。通过在 FormGroup 级别添加自定义验证器,对多个 FormControl 的值进行比较和验证。

7. 动态表单验证

7.1 根据条件添加或移除验证规则

有时候,验证规则需要根据用户的操作动态变化。例如,在一个注册表单中,如果用户选择了“使用公司邮箱注册”,则邮箱输入框需要验证是否为公司域名的邮箱。 首先,在组件中定义表单和相关逻辑:

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

@Component({
  selector: 'app - register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent {
  registerForm: FormGroup;
  useCompanyEmail = false;

  constructor() {
    this.registerForm = new FormGroup({
      email: new FormControl('', Validators.required),
      useCompanyEmailCheckbox: new FormControl(false)
    });

    this.registerForm.get('useCompanyEmailCheckbox').valueChanges.subscribe((value) => {
      this.useCompanyEmail = value;
      const emailControl = this.registerForm.get('email');
      if (value) {
        emailControl.setValidators([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@company\\.com$')]);
      } else {
        emailControl.setValidators(Validators.required);
      }
      emailControl.updateValueAndValidity();
    });
  }
}

在模板中:

<form [formGroup]="registerForm">
  <div class="form - group">
    <label for="email">邮箱</label>
    <input type="email" id="email" formControlName="email">
    <div *ngIf="registerForm.get('email').hasError('required') && (registerForm.get('email').touched || registerForm.get('email').dirty)">
      邮箱不能为空
    </div>
    <div *ngIf="useCompanyEmail && registerForm.get('email').hasError('pattern') && (registerForm.get('email').touched || registerForm.get('email').dirty)">
      请使用公司邮箱注册
    </div>
  </div>
  <div class="form - group">
    <input type="checkbox" formControlName="useCompanyEmailCheckbox">使用公司邮箱注册
  </div>
</form>

在上述代码中,当用户勾选“使用公司邮箱注册”时,为邮箱输入框添加特定的域名验证规则;取消勾选时,移除该规则并只保留必填验证。

7.2 动态生成表单及验证

在某些场景下,表单的字段和验证规则需要根据后端数据动态生成。例如,一个调查问卷应用,问卷的题目和验证要求从服务器获取。 假设从服务器获取的数据格式如下:

[
  {
    "id": 1,
    "question": "你的姓名",
    "type": "text",
    "validators": ["required"]
  },
  {
    "id": 2,
    "question": "你的年龄",
    "type": "number",
    "validators": ["required", "min:18"]
  }
]

在组件中动态生成表单:

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

@Component({
  selector: 'app - survey',
  templateUrl: './survey.component.html',
  styleUrls: ['./survey.component.css']
})
export class SurveyComponent {
  surveyForm: FormGroup;
  surveyQuestions = [];

  constructor() {
    // 模拟从服务器获取数据
    this.surveyQuestions = [
      {
        "id": 1,
        "question": "你的姓名",
        "type": "text",
        "validators": ["required"]
      },
      {
        "id": 2,
        "question": "你的年龄",
        "type": "number",
        "validators": ["required", "min:18"]
      }
    ];

    this.surveyForm = new FormGroup({});
    this.surveyQuestions.forEach(question => {
      let validators = [];
      question.validators.forEach(validator => {
        if (validator ==='required') {
          validators.push(Validators.required);
        } else if (validator.startsWith('min:')) {
          const minValue = parseInt(validator.split(':')[1], 10);
          validators.push(Validators.min(minValue));
        }
      });
      this.surveyForm.addControl(`question${question.id}`, new FormControl('', validators));
    });
  }
}

在模板中显示表单:

<form [formGroup]="surveyForm">
  <div *ngFor="let question of surveyQuestions" class="form - group">
    <label>{{question.question}}</label>
    <ng - container [ngSwitch]="question.type">
      <input *ngSwitchCase="'text'" type="text" [formControlName]="'question' + question.id">
      <input *ngSwitchCase="'number'" type="number" [formControlName]="'question' + question.id">
    </ng - container>
    <div *ngIf="surveyForm.get('question' + question.id).hasError('required') && (surveyForm.get('question' + question.id).touched || surveyForm.get('question' + question.id).dirty)">
      该项为必填
    </div>
    <div *ngIf="surveyForm.get('question' + question.id).hasError('min') && (surveyForm.get('question' + question.id).touched || surveyForm.get('question' + question.id).dirty)">
      请输入不小于 {{surveyForm.get('question' + question.id).getError('min').min}} 的值
    </div>
  </div>
  <button type="submit" [disabled]="!surveyForm.valid">提交</button>
</form>

通过这种方式,我们可以根据后端数据动态生成表单并应用相应的验证规则。

8. 总结

Angular 的表单验证机制为前端开发者提供了丰富且强大的工具来确保用户输入数据的有效性。从基本的内置验证规则到自定义验证器,再到动态表单验证和跨字段验证,Angular 能够满足各种复杂的业务需求。通过合理运用这些验证机制,我们可以提高用户体验,减少无效数据的提交,保证应用程序的稳定性和数据的准确性。在实际开发中,需要根据具体的业务场景选择合适的表单类型(模板驱动表单或响应式表单),并灵活应用各种验证规则和技巧,以构建出高质量的前端表单。同时,要注意错误处理和提示的友好性,让用户能够清晰地了解输入错误并及时纠正。掌握 Angular 表单验证的常见规则与应用,是成为一名优秀 Angular 开发者的重要一步。