2.7表单 (Forms) 2.7 表单 (Forms) 表单是现代 Web 应用中不可或缺的一部分。它们允许用户与应用程序交互,输入数据,并提交请求。Angular 提供了两种构建表单的主要方法:模板驱动表单 (Template-driven Forms) 和 响应式表单 (Reactive Forms)。 2.7.1 表单类型选择 选择哪种表单类型取决于项目的需求、复杂性和开发团队的偏好。 模板驱动表单: 易于上手,适合简单的表单场景。主要依赖于 HTML 模板中的指令来管理表单状态和验证。 响应式表单: 更加灵活和强大,适合复杂的表单场景。通过在组件类中定义表单模型,可以更好地控制表单行为和数据流。
表单是现代 Web 应用中不可或缺的一部分。它们允许用户与应用程序交互,输入数据,并提交请求。Angular 提供了两种构建表单的主要方法:模板驱动表单 (Template-driven Forms) 和 响应式表单 (Reactive Forms)。
选择哪种表单类型取决于项目的需求、复杂性和开发团队的偏好。
模板驱动表单: 易于上手,适合简单的表单场景。主要依赖于 HTML 模板中的指令来管理表单状态和验证。
响应式表单: 更加灵活和强大,适合复杂的表单场景。通过在组件类中定义表单模型,可以更好地控制表单行为和数据流。
| 特性 | 模板驱动表单 | 响应式表单 |
|---|---|---|
| 模块 | FormsModule |
ReactiveFormsModule |
| 表单定义 | 在模板中使用指令 | 在组件类中定义 FormGroup 和 FormControl |
| 数据流 | 隐式,通过双向数据绑定 ngModel |
显式,通过 valueChanges Observable |
| 验证 | 基于指令和模板 | 基于 Validators 和组件类 |
| 适用场景 | 简单表单,快速原型开发 | 复杂表单,需要高度控制和可测试性 |
| 可测试性 | 较低 | 较高 |
以下是一个 Mermaid 图表,展示了两种表单类型的区别:
模板驱动表单通过在 HTML 模板中使用 ngModel 指令来实现双向数据绑定和表单状态管理。
代码实践:
导入 FormsModule:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; // 导入 FormsModule import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, FormsModule], // 将 FormsModule 添加到 imports 数组 providers: [], bootstrap: [AppComponent], }) export class AppModule {}
创建表单模板:
<!-- src/app/app.component.html --> <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> <div> <label for="name">Name:</label> <input type="text" id="name" name="name" [(ngModel)]="user.name" required #name="ngModel"> <div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger"> <div *ngIf="name.errors?.['required']"> Name is required. </div> </div> </div> <div> <label for="email">Email:</label> <input type="email" id="email" name="email" [(ngModel)]="user.email" email #email="ngModel"> <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger"> <div *ngIf="email.errors?.['email']"> Email is invalid. </div> </div> </div> <button type="submit" [disabled]="myForm.invalid">Submit</button> </form> <p>Form Value: {{ myForm.value | json }}</p> <p>Form Valid: {{ myForm.valid }}</p>
创建组件类:
// src/app/app.component.ts import { Component } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { user = { name: '', email: '', }; onSubmit(form: NgForm) { if (form.valid) { console.log('Form submitted:', this.user); // 在此处处理表单提交逻辑 } else { console.log('Form is invalid'); } } }
内容详解:
FormsModule: 提供了 ngModel 指令和其他用于模板驱动表单的必要组件。
ngModel: 用于在表单控件和组件类中的属性之间建立双向数据绑定。它会自动跟踪控件的值、验证状态和用户交互。
#myForm="ngForm": 创建一个对 NgForm 指令的模板引用变量。 NgForm 指令会自动跟踪表单的状态,包括有效性、脏状态和触摸状态。
required 和 email: HTML5 内置的验证属性。Angular 的 ngModel 指令会自动利用这些属性进行验证。
#name="ngModel": 创建一个对 ngModel 指令的模板引用变量,用于访问特定控件的状态和错误。
*ngIf="name.invalid && (name.dirty || name.touched)": 仅当控件无效且用户已与控件交互(脏状态或触摸状态)时,才显示验证错误消息。
myForm.value: 包含了表单中所有控件的值的对象。
myForm.valid: 指示表单是否有效。
(ngSubmit): 当表单提交时触发的事件。
响应式表单通过在组件类中定义表单模型,并使用 FormGroup、FormControl 和 FormBuilder 等类来管理表单状态和验证。
代码实践:
导入 ReactiveFormsModule:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; // 导入 ReactiveFormsModule import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, ReactiveFormsModule], // 将 ReactiveFormsModule 添加到 imports 数组 providers: [], bootstrap: [AppComponent], }) export class AppModule {}
创建组件类:
// src/app/app.component.ts import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { myForm!: FormGroup; // 使用 ! 断言变量已被赋值 constructor(private fb: FormBuilder) {} ngOnInit() { this.myForm = this.fb.group({ name: ['', Validators.required], email: ['', [Validators.required, Validators.email]], address: this.fb.group({ street: [''], city: [''], }), }); // 监听表单值的变化 this.myForm.valueChanges.subscribe(value => { console.log('Form Value Changes:', value); }); // 监听表单状态的变化 this.myForm.statusChanges.subscribe(status => { console.log('Form Status Changes:', status); }); } onSubmit() { if (this.myForm.valid) { console.log('Form submitted:', this.myForm.value); // 在此处处理表单提交逻辑 } else { console.log('Form is invalid'); } } }
创建表单模板:
<!-- src/app/app.component.html --> <form [formGroup]="myForm" (ngSubmit)="onSubmit()"> <div> <label for="name">Name:</label> <input type="text" id="name" formControlName="name"> <div *ngIf="myForm.get('name')?.invalid && (myForm.get('name')?.dirty || myForm.get('name')?.touched)" class="alert alert-danger"> <div *ngIf="myForm.get('name')?.errors?.['required']"> Name is required. </div> </div> </div> <div> <label for="email">Email:</label> <input type="email" id="email" formControlName="email"> <div *ngIf="myForm.get('email')?.invalid && (myForm.get('email')?.dirty || myForm.get('email')?.touched)" class="alert alert-danger"> <div *ngIf="myForm.get('email')?.errors?.['required']"> Email is required. </div> <div *ngIf="myForm.get('email')?.errors?.['email']"> Email is invalid. </div> </div> </div> <div formGroupName="address"> <div> <label for="street">Street:</label> <input type="text" id="street" formControlName="street"> </div> <div> <label for="city">City:</label> <input type="text" id="city" formControlName="city"> </div> </div> <button type="submit" [disabled]="myForm.invalid">Submit</button> </form> <p>Form Value: {{ myForm.value | json }}</p> <p>Form Valid: {{ myForm.valid }}</p>
内容详解:
ReactiveFormsModule: 提供了 FormGroup、FormControl、FormBuilder 和 Validators 等类,用于构建和管理响应式表单。
FormGroup: 表示一个表单组,它包含一个或多个 FormControl 实例。
FormControl: 表示一个单独的表单控件,例如文本框、下拉列表或复选框。
FormBuilder: 一个便捷的类,用于创建 FormGroup 和 FormControl 实例。
Validators: 提供了一组内置的验证器,例如 required、email、minLength 和 maxLength。
formGroup: 用于将 FormGroup 实例绑定到 HTML 表单元素。
formControlName: 用于将 FormControl 实例绑定到特定的 HTML 表单控件。
myForm.value: 包含了表单中所有控件的值的对象。
myForm.valid: 指示表单是否有效。
valueChanges: 一个 Observable,它在表单值发生变化时发出事件。
statusChanges: 一个 Observable,它在表单状态(例如有效性)发生变化时发出事件。
formGroupName: 用于创建嵌套的表单组,例如 address。
myForm.get('name'): 用于访问表单中特定控件的 AbstractControl 实例。
Angular 允许创建自定义验证器来满足特定的验证需求。
代码实践:
创建自定义验证器函数:
// src/app/custom-validators.ts import { AbstractControl, ValidatorFn } from '@angular/forms'; export function forbiddenNameValidator(forbiddenName: RegExp): ValidatorFn { return (control: AbstractControl): { [key: string]: any } | null => { const forbidden = forbiddenName.test(control.value); return forbidden ? { forbiddenName: { value: control.value } } : null; }; }
在组件中使用自定义验证器:
// src/app/app.component.ts import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { forbiddenNameValidator } from './custom-validators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { myForm!: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.myForm = this.fb.group({ name: ['', [Validators.required, forbiddenNameValidator(/admin/i)]], email: ['', [Validators.required, Validators.email]], }); } onSubmit() { if (this.myForm.valid) { console.log('Form submitted:', this.myForm.value); } else { console.log('Form is invalid'); } } }
在模板中显示自定义验证错误消息:
<!-- src/app/app.component.html --> <div> <label for="name">Name:</label> <input type="text" id="name" formControlName="name"> <div *ngIf="myForm.get('name')?.invalid && (myForm.get('name')?.dirty || myForm.get('name')?.touched)" class="alert alert-danger"> <div *ngIf="myForm.get('name')?.errors?.['required']"> Name is required. </div> <div *ngIf="myForm.get('name')?.errors?.['forbiddenName']"> Name cannot contain "admin". </div> </div> </div>
内容详解:
forbiddenNameValidator: 一个自定义验证器函数,它接受一个正则表达式作为参数,并返回一个 ValidatorFn。
ValidatorFn: 一个函数,它接受一个 AbstractControl 作为参数,并返回一个错误对象或 null。
forbiddenName.test(control.value): 使用正则表达式测试控件的值是否包含禁止的名称。
{ forbiddenName: { value: control.value } }: 如果控件的值包含禁止的名称,则返回一个错误对象。
myForm.get('name')?.errors?.['forbiddenName']: 用于访问自定义验证器的错误消息。
异步验证器用于执行需要异步操作的验证,例如检查用户名是否已存在于数据库中。
代码实践:
创建异步验证器函数:
// src/app/async-validators.ts import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { map, delay } from 'rxjs/operators'; export function userNameExistsValidator(): AsyncValidatorFn { return (control: AbstractControl): Observable<ValidationErrors | null> => { return checkUserNameExists(control.value).pipe( map(exists => (exists ? { userNameExists: true } : null)), delay(1000) // 模拟网络延迟 ); }; } // 模拟检查用户名是否存在的服务 function checkUserNameExists(userName: string): Observable<boolean> { const existingUserNames = ['john', 'jane']; const exists = existingUserNames.includes(userName); return of(exists); }
在组件中使用异步验证器:
// src/app/app.component.ts import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { userNameExistsValidator } from './async-validators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { myForm!: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.myForm = this.fb.group({ name: ['', Validators.required, userNameExistsValidator()], email: ['', [Validators.required, Validators.email]], }); } onSubmit() { if (this.myForm.valid) { console.log('Form submitted:', this.myForm.value); } else { console.log('Form is invalid'); } } }
在模板中显示异步验证错误消息:
<!-- src/app/app.component.html --> <div> <label for="name">Name:</label> <input type="text" id="name" formControlName="name"> <div *ngIf="myForm.get('name')?.invalid && (myForm.get('name')?.dirty || myForm.get('name')?.touched)" class="alert alert-danger"> <div *ngIf="myForm.get('name')?.errors?.['required']"> Name is required. </div> <div *ngIf="myForm.get('name')?.errors?.['userNameExists']"> User name already exists. </div> </div> </div>
内容详解:
userNameExistsValidator: 一个异步验证器函数,它返回一个 AsyncValidatorFn。
AsyncValidatorFn: 一个函数,它接受一个 AbstractControl 作为参数,并返回一个 Observable<ValidationErrors | null>。
checkUserNameExists(control.value): 一个模拟的异步函数,它检查用户名是否已存在。
map(exists => (exists ? { userNameExists: true } : null)): 将异步操作的结果转换为一个错误对象或 null。
delay(1000): 模拟网络延迟。
Angular 的表单功能提供了强大的工具来构建和管理各种类型的表单。 模板驱动表单适合简单的场景,而响应式表单则更适合复杂的场景,并提供更好的控制和可测试性。 了解如何使用自定义验证器和异步验证器可以进一步扩展表单的功能,以满足特定的需求。