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

Angular表单验证的错误信息显示与处理

2024-09-016.5k 阅读

Angular 表单验证的错误信息显示与处理

在 Angular 应用开发中,表单验证是确保用户输入数据合法性的关键环节。当用户输入不符合预期格式或规则的数据时,如何准确且友好地显示错误信息,并进行恰当的处理,是提升用户体验的重要方面。

模板驱动表单的错误信息显示与处理

1. 基本表单验证与错误信息绑定

模板驱动表单是 Angular 中一种较为便捷的表单创建方式。以一个简单的登录表单为例,我们通常会对用户名和密码字段进行验证。假设用户名不能为空,密码长度至少为 6 位。

首先,在 HTML 模板文件(如 login.component.html)中创建表单:

<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" name="username" [(ngModel)]="user.username" required />
    <div *ngIf="loginForm.controls['username'].hasError('required') && (loginForm.controls['username'].touched || loginForm.controls['username'].dirty)">
      用户名不能为空
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" name="password" [(ngModel)]="user.password" minlength="6" />
    <div *ngIf="loginForm.controls['password'].hasError('minlength') && (loginForm.controls['password'].touched || loginForm.controls['password'].dirty)">
      密码长度至少为 6 位
    </div>
  </div>
  <button type="submit" class="btn btn-primary">登录</button>
</form>

在上述代码中,我们使用 requiredminlength 等 HTML5 原生验证属性对输入字段进行验证。通过 *ngIf 指令结合 hasError 方法来判断表单控件是否存在特定错误,并在用户触摸或修改字段后显示相应的错误信息。

2. 自定义验证错误信息与处理

有时,原生验证规则无法满足业务需求,我们需要自定义验证。比如,用户名只能包含字母和数字,不能包含特殊字符。

首先,创建一个自定义验证器函数(如在 shared/custom-validators.ts 文件中):

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

export function noSpecialCharsValidator(control: AbstractControl): { [key: string]: any } | null {
  const specialCharsRegex = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/;
  if (specialCharsRegex.test(control.value)) {
    return { hasSpecialChars: true };
  }
  return null;
}

然后在模板中应用这个自定义验证器:

<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" name="username" [(ngModel)]="user.username" required appNoSpecialChars />
    <div *ngIf="loginForm.controls['username'].hasError('required') && (loginForm.controls['username'].touched || loginForm.controls['username'].dirty)">
      用户名不能为空
    </div>
    <div *ngIf="loginForm.controls['username'].hasError('hasSpecialChars') && (loginForm.controls['username'].touched || loginForm.controls['username'].dirty)">
      用户名不能包含特殊字符
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" name="password" [(ngModel)]="user.password" minlength="6" />
    <div *ngIf="loginForm.controls['password'].hasError('minlength') && (loginForm.controls['password'].touched || loginForm.controls['password'].dirty)">
      密码长度至少为 6 位
    </div>
  </div>
  <button type="submit" class="btn btn-primary">登录</button>
</form>

这里,我们通过 appNoSpecialChars 指令将自定义验证器应用到用户名输入框。并在模板中添加了相应的错误信息显示逻辑。

3. 表单整体验证状态与错误处理

模板驱动表单有一个整体的验证状态,我们可以利用它来处理整个表单提交时的错误情况。

在组件类(login.component.ts)中:

import { Component } from '@angular/core';

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

  onSubmit(form) {
    if (form.valid) {
      // 处理表单提交逻辑,如发送请求到后端
      console.log('表单有效,提交数据:', this.user);
    } else {
      // 可以在这里对整个表单的错误进行统一处理,例如显示一个全局提示
      console.log('表单无效,请检查输入');
    }
  }
}

响应式表单的错误信息显示与处理

1. 响应式表单基础验证与错误信息展示

响应式表单提供了更灵活和强大的表单控制能力。以一个注册表单为例,我们来看看如何进行验证和错误信息显示。

首先,在组件类(register.component.ts)中创建响应式表单:

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;

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

  get username() { return this.registerForm.get('username'); }
  get email() { return this.registerForm.get('email'); }
  get password() { return this.registerForm.get('password'); }
}

然后在 HTML 模板文件(register.component.html)中显示错误信息:

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" formControlName="username" />
    <div *ngIf="username.hasError('required') && (username.touched || username.dirty)">
      用户名不能为空
    </div>
    <div *ngIf="username.hasError('minLength') && (username.touched || username.dirty)">
      用户名长度至少为 3 位
    </div>
  </div>
  <div class="form-group">
    <label for="email">邮箱</label>
    <input type="email" id="email" formControlName="email" />
    <div *ngIf="email.hasError('required') && (email.touched || email.dirty)">
      邮箱不能为空
    </div>
    <div *ngIf="email.hasError('email') && (email.touched || email.dirty)">
      请输入正确的邮箱格式
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" formControlName="password" />
    <div *ngIf="password.hasError('required') && (password.touched || password.dirty)">
      密码不能为空
    </div>
    <div *ngIf="password.hasError('minLength') && (password.touched || password.dirty)">
      密码长度至少为 6 位
    </div>
  </div>
  <button type="submit" class="btn btn-primary">注册</button>
</form>

与模板驱动表单类似,我们通过 formControlName 绑定表单控件,并使用 hasError 方法结合 *ngIf 指令来显示错误信息。

2. 响应式表单自定义验证及错误处理

同样,响应式表单也支持自定义验证。假设我们要验证两次输入的密码是否一致。

先创建自定义验证器函数(如在 shared/custom-validators.ts 文件中):

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;
}

然后在组件类中应用自定义验证器:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { passwordMatchValidator } from '../shared/custom-validators';

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

  constructor() {
    this.registerForm = new FormGroup({
      'username': new FormControl('', [Validators.required, Validators.minLength(3)]),
      'email': new FormControl('', [Validators.required, Validators.email]),
      'password': new FormControl('', [Validators.required, Validators.minLength(6)]),
      'confirmPassword': new FormControl('', [Validators.required])
    }, { validators: passwordMatchValidator });
  }

  get username() { return this.registerForm.get('username'); }
  get email() { return this.registerForm.get('email'); }
  get password() { return this.registerForm.get('password'); }
  get confirmPassword() { return this.registerForm.get('confirmPassword'); }
}

在模板中显示错误信息:

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" formControlName="username" />
    <div *ngIf="username.hasError('required') && (username.touched || username.dirty)">
      用户名不能为空
    </div>
    <div *ngIf="username.hasError('minLength') && (username.touched || username.dirty)">
      用户名长度至少为 3 位
    </div>
  </div>
  <div class="form-group">
    <label for="email">邮箱</label>
    <input type="email" id="email" formControlName="email" />
    <div *ngIf="email.hasError('required') && (email.touched || email.dirty)">
      邮箱不能为空
    </div>
    <div *ngIf="email.hasError('email') && (email.touched || email.dirty)">
      请输入正确的邮箱格式
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" formControlName="password" />
    <div *ngIf="password.hasError('required') && (password.touched || password.dirty)">
      密码不能为空
    </div>
    <div *ngIf="password.hasError('minLength') && (password.touched || password.dirty)">
      密码长度至少为 6 位
    </div>
  </div>
  <div class="form-group">
    <label for="confirmPassword">确认密码</label>
    <input type="password" id="confirmPassword" formControlName="confirmPassword" />
    <div *ngIf="registerForm.hasError('passwordMismatch') && (confirmPassword.touched || confirmPassword.dirty)">
      两次输入的密码不一致
    </div>
  </div>
  <button type="submit" class="btn btn-primary">注册</button>
</form>

这里注意,由于 passwordMatchValidator 是应用在整个表单组上的验证器,所以我们通过 registerForm.hasError 来判断错误。

3. 响应式表单整体验证状态及错误处理

与模板驱动表单类似,响应式表单也有整体的验证状态。在组件类中处理表单提交时:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { passwordMatchValidator } from '../shared/custom-validators';

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

  constructor() {
    this.registerForm = new FormGroup({
      'username': new FormControl('', [Validators.required, Validators.minLength(3)]),
      'email': new FormControl('', [Validators.required, Validators.email]),
      'password': new FormControl('', [Validators.required, Validators.minLength(6)]),
      'confirmPassword': new FormControl('', [Validators.required])
    }, { validators: passwordMatchValidator });
  }

  get username() { return this.registerForm.get('username'); }
  get email() { return this.registerForm.get('email'); }
  get password() { return this.registerForm.get('password'); }
  get confirmPassword() { return this.registerForm.get('confirmPassword'); }

  onSubmit() {
    if (this.registerForm.valid) {
      // 处理表单提交逻辑,如发送请求到后端
      console.log('表单有效,提交数据:', this.registerForm.value);
    } else {
      // 可以在这里对整个表单的错误进行统一处理,例如显示一个全局提示
      console.log('表单无效,请检查输入');
    }
  }
}

国际化错误信息显示

在国际化的应用中,我们需要根据用户的语言环境显示不同语言的错误信息。Angular 提供了 @angular/localize 模块来帮助我们实现这一功能。

1. 配置国际化支持

首先,在项目的 angular.json 文件中启用国际化支持:

{
  "architect": {
    "build": {
      "builder": "@angular - localize:browser",
      "options": {
        "localize": ["en", "zh - CN"],
        // 其他原有的配置项...
      }
    },
    "serve": {
      "builder": "@angular - localize:browser - preview",
      "options": {
        // 其他原有的配置项...
      }
    }
  }
}

然后安装 @angular/localize 包:

npm install @angular/localize

2. 标记错误信息以便翻译

在模板文件中,我们使用 i18n 属性标记需要翻译的错误信息。例如在注册表单模板中:

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" formControlName="username" />
    <div *ngIf="username.hasError('required') && (username.touched || username.dirty)" i18n>
      用户名不能为空
    </div>
    <div *ngIf="username.hasError('minLength') && (username.touched || username.dirty)" i18n>
      用户名长度至少为 3 位
    </div>
  </div>
  <div class="form-group">
    <label for="email">邮箱</label>
    <input type="email" id="email" formControlName="email" />
    <div *ngIf="email.hasError('required') && (email.touched || email.dirty)" i18n>
      邮箱不能为空
    </div>
    <div *ngIf="email.hasError('email') && (email.touched || email.dirty)" i18n>
      请输入正确的邮箱格式
    </div>
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" id="password" formControlName="password" />
    <div *ngIf="password.hasError('required') && (password.touched || password.dirty)" i18n>
      密码不能为空
    </div>
    <div *ngIf="password.hasError('minLength') && (password.touched || password.dirty)" i18n>
      密码长度至少为 6 位
    </div>
  </div>
  <div class="form-group">
    <label for="confirmPassword">确认密码</label>
    <input type="password" id="confirmPassword" formControlName="confirmPassword" />
    <div *ngIf="registerForm.hasError('passwordMismatch') && (confirmPassword.touched || confirmPassword.dirty)" i18n>
      两次输入的密码不一致
    </div>
  </div>
  <button type="submit" class="btn btn-primary">注册</button>
</form>

3. 生成翻译文件及翻译

运行以下命令生成翻译文件:

ng xi18n --output - path src/locale

这会在 src/locale 目录下生成 messages.xlf 文件,其中包含了我们标记的需要翻译的内容。我们可以根据不同语言创建对应的 messages.[语言代码].xlf 文件,例如 messages.en.xlfmessages.zh - CN.xlf,并在其中进行翻译。

例如 messages.en.xlf 文件:

<?xml version="1.0" encoding="UTF - 8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source - language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans - unit id="1">
        <source>用户名不能为空</source>
        <target>The username cannot be empty</target>
      </trans - unit>
      <trans - unit id="2">
        <source>用户名长度至少为 3 位</source>
        <target>The username must be at least 3 characters long</target>
      </trans - unit>
      <!-- 其他翻译内容... -->
    </body>
  </file>
</xliff>

4. 根据语言环境加载翻译

在应用的启动过程中,我们需要根据用户的语言环境加载相应的翻译。可以在 app.module.ts 中配置:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import en from '@angular/common/locales/en';
import { LOCALE_ID } from '@angular/core';
import { TranslateModule, TranslateLoader } from '@ngx - translate/core';
import { TranslateHttpLoader } from '@ngx - translate/http - loader';
import { HttpClient, HttpClientModule } from '@angular/common/http';

registerLocaleData(zh);
registerLocaleData(en);

export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: createTranslateLoader,
        deps: [HttpClient]
      }
    })
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'en' } // 可以根据实际情况动态设置语言
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

然后在模板中使用 ngx - translate 来显示翻译后的错误信息。例如:

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" id="username" formControlName="username" />
    <div *ngIf="username.hasError('required') && (username.touched || username.dirty)">
      {{ 'USERNAME_REQUIRED' | translate }}
    </div>
    <div *ngIf="username.hasError('minLength') && (username.touched || username.dirty)">
      {{ 'USERNAME_MIN_LENGTH' | translate }}
    </div>
  </div>
  <!-- 其他字段类似处理 -->
</form>

并在对应的语言 JSON 文件(如 src/assets/i18n/en.json)中定义翻译内容:

{
  "USERNAME_REQUIRED": "The username cannot be empty",
  "USERNAME_MIN_LENGTH": "The username must be at least 3 characters long",
  // 其他翻译内容
}

错误信息样式与用户体验优化

错误信息的显示样式对于用户体验至关重要。合理的样式可以让用户更清晰地识别错误,并快速进行修正。

1. 错误信息的颜色与字体样式

通常,我们会使用醒目的颜色来突出错误信息,比如红色。同时,可以调整字体大小和粗细,使其更加明显。

在 CSS 文件(如 styles.css)中:

.form - error {
  color: red;
  font - size: 14px;
  font - weight: bold;
}

然后在模板中应用这个类:

<div *ngIf="username.hasError('required') && (username.touched || username.dirty)" class="form - error">
  用户名不能为空
</div>

2. 错误信息的位置与布局

错误信息应该紧邻对应的输入字段,这样用户可以很容易地将错误与输入联系起来。对于复杂的表单布局,可以使用定位或 Flexbox、Grid 等布局方式来确保错误信息的正确位置。

例如,使用 Flexbox 布局:

<div class="form - group" style="display: flex; flex - direction: column">
  <label for="username">用户名</label>
  <input type="text" id="username" formControlName="username" />
  <div *ngIf="username.hasError('required') && (username.touched || username.dirty)" class="form - error">
    用户名不能为空
  </div>
</div>

3. 错误提示的动画效果

适当的动画效果可以增强用户体验。比如,当错误信息出现时,可以使用淡入动画,当错误信息消失时,可以使用淡出动画。

使用 Angular 的 @angular/animations 模块来实现:

在组件类(如 register.component.ts)中引入动画模块:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  selector: 'app - register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css'],
  animations: [
    trigger('errorFade', [
      state('void', style({ opacity: 0 })),
      transition(':enter', [animate('300ms ease - in')]),
      transition(':leave', [animate('300ms ease - out')])
    ])
  ]
})
export class RegisterComponent {
  // 表单相关代码...
}

在模板中应用动画:

<div *ngIf="username.hasError('required') && (username.touched || username.dirty)" class="form - error" [@errorFade]>
  用户名不能为空
</div>

这样,当错误信息出现或消失时,会有一个淡入淡出的动画效果,让用户交互更加流畅和友好。

通过以上对 Angular 表单验证错误信息显示与处理的详细介绍,从模板驱动表单和响应式表单的基础验证、自定义验证,到国际化处理以及错误信息样式与用户体验优化,开发者可以构建出功能完善且用户友好的表单系统,为应用的质量和用户满意度提供有力保障。