表单
在 Angular 中,表单有两种类型,分别为模板驱动表单和响应式表单。
模板驱动表单
概述
表单的控制逻辑写在组件模板中,适合简单的表单类型。
快速上手
引入依赖模块 FormsModule
import { FormsModule } from "@angular/forms" @NgModule({ imports: [FormsModule], }) export class AppModule {}将 DOM 表单转换为
ngForm<form #f="ngForm" (submit)="onSubmit(f)"></form>声明表单字段为
ngModel<form #f="ngForm" (submit)="onSubmit(f)"> <input type="text" name="username" ngModel /> <button>提交</button> </form>获取表单字段值
import { NgForm } from "@angular/forms" export class AppComponent { onSubmit(form: NgForm) { console.log(form.value) } }表单分组
当你的表单的表单项比较多,可以把相关的表单项分组。
<form #f="ngForm" (submit)="onSubmit(f)"> <div ngModelGroup="user"> <input type="text" name="username" ngModel /> </div> <div ngModelGroup="contact"> <input type="text" name="phone" ngModel /> </div> <button>提交</button> </form>分组后,访问表单数据,例如
username,就是form.user.username。
表单验证
required必填字段minlength字段最小长度maxlength字段最大长度pattern验证正则 例如:pattern="\d"匹配一个数值
<form #f="ngForm" (submit)="onSubmit(f)">
<input type="text" name="username" ngModel required pattern="\d" />
<button>提交</button>
</form>export class AppComponent {
onSubmit(form: NgForm) {
// 查看表单整体是否验证通过
console.log(form.valid)
}
}<!-- 表单整体未通过验证时禁用提交表单 -->
<button type="submit" [disabled]="f.invalid">提交</button>在组件模板中显示表单项未通过时的错误信息。
<form #f="ngForm" (submit)="onSubmit(f)">
<input #username="ngModel" />
<!-- touched 为 true 表示用户操作过该表单项 -->
<div *ngIf="username.touched && !username.valid && username.errors">
<div *ngIf="username.errors.required">请填写用户名</div>
<div *ngIf="username.errors.pattern">不符合正则规则</div>
</div>
</form>dirty 和 touched 都可以用来判断表单控件是否被用户操作过:
dirty:如果用户修改了表单控件的值,则该控件的dirty状态会被设置为true。如果控件的值没有被修改,dirty状态为false。touched:如果控件失去焦点(即用户点击过该控件并离开),则控件的touched状态会被设置为true。
指定表单项未通过验证时的样式。
input.ng-touched.ng-invalid {
border: 2px solid red;
}响应式表单
概述
表单的控制逻辑写在组件类中,对验证逻辑拥有更多的控制权,适合复杂的表单的类型。
在响应式表单中,表单字段需要是 FormControl 类的实例,实例对象可以验证表单字段中的值,值是否被修改过等等。

一组表单字段构成整个表单,整个表单需要是 FormGroup 类的实例,它可以对表单进行整体验证。

FormControl:表单组中的一个表单项FormGroup:表单组,表单至少是一个FormGroupFormArray:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray中有一项没通过,那么整体就不会通过。
快速上手
引入
ReactiveFormsModuleimport { ReactiveFormsModule } from "@angular/forms" @NgModule({ imports: [ReactiveFormsModule] }) export class AppModule {}在组件类中创建
FormGroup表单控制对象import { FormControl, FormGroup } from "@angular/forms" export class AppComponent { contactForm: FormGroup = new FormGroup({ name: new FormControl(), phone: new FormControl() }) }使用
formGroup来绑定一组表单控件,使用formControlName来绑定每个表单控件。<form [formGroup]="contactForm" (submit)="onSubmit()"> <input type="text" formControlName="name" /> <input type="text" formControlName="phone" /> <button>提交</button> </form>获取表单值
export class AppComponent { onSubmit() { console.log(this.contactForm.value) } }设置表单默认值
contactForm: FormGroup = new FormGroup({ name: new FormControl("默认值"), phone: new FormControl(15888888888) })表单分组
contactForm: FormGroup = new FormGroup({ fullName: new FormGroup({ firstName: new FormControl(), lastName: new FormControl() }), phone: new FormControl() })<form [formGroup]="contactForm" (submit)="onSubmit()"> <div formGroupName="fullName"> <input type="text" formControlName="firstName" /> <input type="text" formControlName="lastName" /> </div> <input type="text" formControlName="phone" /> <button>提交</button> </form>onSubmit() { // 下面两种方式都可以获取到表单项的值 console.log(this.contactForm.value.name.username) console.log(this.contactForm.get(["name", "username"])?.value) }
formControl指令用于将一个单独的FormControl实例绑定到模板中的表单控件。通常在没有 FormGroup 的情况下使用,适合单独处理一个控件,而不需要嵌套在表单组中。<input [formControl]="nameControl" />import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; @Component({ selector: 'app-my-form', templateUrl: './my-form.component.html', }) export class MyFormComponent { nameControl = new FormControl(''); // 创建 FormControl 实例 }formControlName指令用于将一个FormControl实例绑定到FormGroup中的表单控件。这个指令只能用于响应式表单(ReactiveForms),并且必须与FormGroup一起使用。<form [formGroup]="form"> <input formControlName="name" /> </form>import { Component } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'app-my-form', templateUrl: './my-form.component.html', }) export class MyFormComponent { form = new FormGroup({ name: new FormControl(''), // 将 'name' 字段添加到 FormGroup 中 }); }
FormArray
FormArray 是一种特殊类型的 FormGroup,用于管理一组表单控件,通常用于处理动态数量的控件,它允许你动态地添加、移除或更新表单控件。例如,当你需要让用户添加多个相似项(如动态添加多个电子邮件地址、电话号码、或者一组购物车项目)时,FormArray 是理想的选择。
例如实现一个表单,允许用户输入多个电子邮件地址:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent implements OnInit {
emailForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.emailForm = this.fb.group({
emails: this.fb.array([this.createEmailControl()])
});
}
// 创建单个电子邮件控件
createEmailControl(): FormGroup {
return this.fb.group({
email: ['', [Validators.required, Validators.email]]
});
}
// 获取 FormArray
get emailControls(): FormArray {
return this.emailForm.get('emails') as FormArray;
}
// 添加新的电子邮件控件
addEmail() {
this.emailControls.push(this.createEmailControl());
}
// 删除指定索引的电子邮件控件
removeEmail(index: number) {
this.emailControls.removeAt(index);
}
// 提交表单数据
onSubmit() {
if (this.emailForm.valid) {
console.log(this.emailForm.value);
} else {
console.log('Form is invalid');
}
}
}emailForm 是一个包含 emails 的表单组,emails 是一个 FormArray。
createEmailControl() 是一个方法,用来创建一个带有电子邮件验证的 FormGroup。
emailControls 是一个 getter,用来获取 FormArray。
addEmail() 方法用于动态添加新的电子邮件控件到 FormArray。
removeEmail(index) 方法用于删除指定索引的控件。
<form [formGroup]="emailForm" (ngSubmit)="onSubmit()">
<div formArrayName="emails">
<div *ngFor="let email of emailControls.controls; let i = index" [formGroupName]="i">
<label for="email{{ i }}">Email {{ i + 1 }}:</label>
<input id="email{{ i }}" formControlName="email" />
<button type="button" (click)="removeEmail(i)">Remove</button>
<div *ngIf="email.get('email').invalid && email.get('email').touched">
<small *ngIf="email.get('email').hasError('required')">Email is required.</small>
<small *ngIf="email.get('email').hasError('email')">Invalid email format.</small>
</div>
</div>
</div>
<button type="button" (click)="addEmail()">Add Email</button>
<button type="submit" [disabled]="emailForm.invalid">Submit</button>
</form>formArrayName="emails":将FormArray与模板中的对应部分绑定。*ngFor:遍历FormArray中的每个表单控件。formControlName="email":为每个表单控件(电子邮件输入框)绑定一个FormControl。addEmail():点击 “Add Email” 按钮时,调用addEmail()方法来向表单中添加一个新的电子邮件输入框。removeEmail(i):点击 “Remove” 按钮时,调用removeEmail()方法来从表单中删除指定的电子邮件输入框。
内置表单验证器
使用内置验证器提供的验证规则验证表单字段
import { FormControl, FormGroup, Validators } from "@angular/forms" contactForm: FormGroup = new FormGroup({ name: new FormControl("默认值", [ Validators.required, Validators.minLength(2) ]) })获取整体表单是否验证通过
onSubmit() { console.log(this.contactForm.valid) }<!-- 表单整体未验证通过时禁用表单按钮 --> <button [disabled]="contactForm.invalid">提交</button>在组件模板中显示为验证通过时的错误信息
get name() { return this.contactForm.get("name")! }<form [formGroup]="contactForm" (submit)="onSubmit()"> <input type="text" formControlName="name" /> <div *ngIf="name.touched && name.invalid && name.errors"> <div *ngIf="name.errors.required">请填写姓名</div> <div *ngIf="name.errors.maxlength"> 姓名长度不能大于 {{ name.errors.maxlength.requiredLength }} 实际填写长度为 {{ name.errors.maxlength.actualLength }} </div> </div> </form>
自定义同步表单验证器
- 自定义验证器的类型是 TypeScript 类
- 类中包含具体的验证方法,验证方法必须为静态方法
- 验证方法有一个参数 control,类型为
AbstractControl。其实就是FormControl类的实例对象的类型 - 如果验证成功,返回
null - 如果验证失败,返回对象,对象中的属性即为验证标识,值为
true,标识该项验证失败 - 验证方法的返回值为
ValidationErrors | null
import { AbstractControl, ValidationErrors } from "@angular/forms"
export class NameValidators {
// 字段值中不能包含空格
static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
// 验证未通过
if (/\s/.test(control.value)) return { cannotContainSpace: true }
// 验证通过
return null
}
}import { NameValidators } from "./Name.validators"
contactForm: FormGroup = new FormGroup({
name: new FormControl("", [
Validators.required,
NameValidators.cannotContainSpace
])
})<div *ngIf="name.touched && name.invalid && name.errors">
<div *ngIf="name.errors.cannotContainSpace">姓名中不能包含空格</div>
</div>自定义异步表单验证器
异步验证器函数:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { debounceTime, map, catchError, switchMap, take } from 'rxjs/operators';
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
@Injectable({
providedIn: 'root'
})
export class EmailValidatorService {
constructor(private http: HttpClient) {}
// 异步验证器:检查邮箱是否已注册
emailExistsValidator(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (!control.value) {
return of(null); // 如果控件值为空,不做验证
}
// 返回 Observable,模拟 HTTP 请求
return this.http.get<any>(`/api/check-email?email=${control.value}`).pipe(
debounceTime(500), // 防止发送过多请求
map(response => {
// 假设 response 存在 `exists` 字段,表示邮箱是否已经存在
return response.exists ? { emailExists: true } : null;
}),
catchError(() => of(null)) // 如果发生错误(如网络请求失败),返回 null
);
};
}
}AsyncValidatorFn:这是异步验证器的类型,它返回一个Observable或Promise,并且必须解析为ValidationErrors(验证失败时)或null(验证通过)。debounceTime:这是防止发送过多请求的技巧,尤其是在用户输入过程中。通常,在输入停止一段时间后再发起验证请求。catchError:如果网络请求失败或出现错误,返回null表示验证成功。
在组件中使用异步验证器:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EmailValidatorService } from './email-validator.service';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html'
})
export class UserFormComponent implements OnInit {
userForm: FormGroup;
constructor(
private fb: FormBuilder,
private emailValidator: EmailValidatorService
) {}
ngOnInit(): void {
this.userForm = this.fb.group({
email: [
'',
[Validators.required, Validators.email],
[this.emailValidator.emailExistsValidator()]
]
});
}
onSubmit(): void {
if (this.userForm.valid) {
console.log(this.userForm.value);
} else {
console.log('Form is invalid');
}
}
}emailExistsValidator():这是在EmailValidatorService中定义的异步验证器,用于验证用户输入的电子邮件地址是否已存在。[this.emailValidator.emailExistsValidator()]:将异步验证器作为第三个参数传递给FormControl,它会在表单控件的值变化时触发。FormControl的验证顺序:Validators.required和Validators.email是同步验证器,而emailExistsValidator()是异步验证器。
模板代码:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label for="email">Email:</label>
<input id="email" formControlName="email" />
<!-- 错误消息 -->
<div *ngIf="userForm.get('email').hasError('required') && userForm.get('email').touched">
Email is required.
</div>
<div *ngIf="userForm.get('email').hasError('email') && userForm.get('email').touched">
Invalid email format.
</div>
<div *ngIf="userForm.get('email').hasError('emailExists') && userForm.get('email').touched">
This email address is already registered.
</div>
</div>
<button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>FormBuilder
FormBuilder 是一个辅助工具,用于简化和减少 FormControl 和 FormGroup 的冗长创建过程,特别是在需要创建大量表单控件时。通过 FormBuilder,可以更简洁、更易读地构建表单。
this.fb.control:表单项this.fb.group:表单组,表单至少是一个FormGroupthis.fb.array:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray中有一项没通过,那么整体就不会通过。
this.form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
});其他
setValue
用于将整个表单控件的值设置为一个新的值。如果你使用 setValue,必须提供一个完整的对象,该对象必须包含所有控件的值,且顺序和控件名必须与原始表单控件匹配。
formGroup.setValue(value: { [key: string]: any }, options?: { onlySelf?: boolean, emitEvent?: boolean })value:一个包含所有控件的值的对象,必须包含表单中所有控件的值。onlySelf(可选):如果为true,则只会更新当前控件的值,不会影响父表单。emitEvent(可选):如果为false,则不会触发控件的valueChanges事件。
patchValue
patchValue 方法与 setValue 类似,也用于更新表单控件的值。但是,patchValue 允许部分更新表单控件的值。你只需要提供你希望更新的控件的值,而不必提供整个表单控件的值。这对于动态表单或者只需更新部分控件的场景非常有用。
formGroup.patchValue(value: { [key: string]: any }, options?: { onlySelf?: boolean, emitEvent?: boolean })value:一个包含部分控件值的对象,更新该对象中的字段,不需要提供所有控件的值。onlySelf(可选):如果为true,则只会更新当前控件的值,不会影响父表单。emitEvent(可选):如果为false,则不会触发控件的valueChanges事件。
reset
方法用于将表单恢复到初始状态。可以通过它将控件值重置为初始值,并恢复表单的有效性。
valid、invalid
这两个属性用来检查表单是否有效。valid 返回 true 如果表单是有效的,invalid 返回 true 如果表单是无效的。
dirty、pristine
dirty:如果控件的值已经被修改,则返回true,否则返回false。pristine:与dirty相反,如果控件的值从未被修改过,则返回true。
touched、untouched
touched:如果控件已经被触摸(即用户离开了该控件),则返回true。untouched:如果控件未被触摸,则返回true。
markAsTouched()、markAsUntouched()
markAsTouched():将所有控件标记为已触摸。markAsUntouched():将所有控件标记为未触摸。
markAsDirty()、markAsPristine()
markAsDirty():将所有控件标记为已修改。markAsPristine():将所有控件标记为未修改。
enable()、disable()
enable():启用表单控件。disable():禁用表单控件。
hasError()
hasError() 用于检查控件是否存在特定的验证错误。
if (this.formGroup.get('email').hasError('required')) {
console.log('Email is required');
}setErrors()
setErrors() 将控件设置错误,并且可以指定一个错误对象。
formGroup.setErrors(errors: ValidationErrors | null, options?: { emitEvent?: boolean }): voiderrors:一个包含错误信息的对象,或者为null,表示清除所有错误。ValidationErrors是一个键值对对象,键是错误的名称,值是错误的描述信息。options.emitEvent(可选):一个布尔值,表示是否触发statusChanges和valueChanges事件。默认为true,即触发事件。
this.formGroup.setErrors({
invalidForm: 'The form is invalid'
});valueChanges、statusChanges
valueChanges:当表单控件的值发生变化时(例如setValue()),会触发该Observable。statusChanges:当表单控件的状态发生变化时(例如从valid到invalid,或者调用setErrors()),会触发该Observable。
this.formGroup.valueChanges.subscribe(value => {
console.log('Form value changed:', value);
});
this.formGroup.statusChanges.subscribe(status => {
console.log('Form status changed:', status);
});updateValueAndValidity
用于更新表单控件的值和有效性。它会触发控件的验证过程,重新评估控件的值是否有效,并更新控件的验证状态(如 valid、invalid、pending、touched 等)。
这个方法在你手动修改控件的值或验证器时特别有用,确保控件的状态得到重新评估。
formControl.updateValueAndValidity(options?: { onlySelf?: boolean, emitEvent?: boolean }): voidonlySelf(可选):如果为true,仅会更新当前控件,而不会影响父表单。默认为false,即更新当前控件及其父控件。emitEvent(可选):如果为false,则不会触发valueChanges或statusChanges事件。默认为true,即会触发这些事件。