响应式表单 #
一、响应式表单概述 #
响应式表单使用模型驱动的方式管理表单状态,提供更灵活的表单控制能力。
text
模板驱动表单 响应式表单
┌─────────────────┐ ┌─────────────────┐
│ 模板 │ │ 组件类 │
│ ngModel │ │ FormControl │
│ ngForm │ │ FormGroup │
└─────────────────┘ │ FormArray │
└─────────────────┘
简单直观 灵活可控
二、基础配置 #
2.1 导入ReactiveFormsModule #
typescript
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `...`
})
export class UserFormComponent {}
三、FormControl #
3.1 创建FormControl #
typescript
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-example',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<input [formControl]="nameControl" />
<p>值: {{ nameControl.value }}</p>
<p>有效: {{ nameControl.valid }}</p>
`
})
export class ExampleComponent {
nameControl = new FormControl('');
}
3.2 FormControl配置 #
typescript
// 带初始值
nameControl = new FormControl('初始值');
// 带验证器
nameControl = new FormControl('', [Validators.required, Validators.minLength(3)]);
// 带配置对象
nameControl = new FormControl('', {
validators: [Validators.required],
asyncValidators: [this.asyncValidator.bind(this)],
updateOn: 'blur'
});
// 禁用状态
disabledControl = new FormControl({ value: '', disabled: true });
3.3 FormControl方法 #
typescript
// 获取值
const value = this.nameControl.value;
// 设置值
this.nameControl.setValue('新值');
// 补丁更新(FormGroup中使用)
this.nameControl.patchValue('新值');
// 重置
this.nameControl.reset();
this.nameControl.reset('默认值');
// 启用/禁用
this.nameControl.enable();
this.nameControl.disable();
// 标记状态
this.nameControl.markAsTouched();
this.nameControl.markAsDirty();
this.nameControl.markAsPristine();
// 设置错误
this.nameControl.setErrors({ customError: true });
// 清除错误
this.nameControl.setErrors(null);
四、FormGroup #
4.1 创建FormGroup #
typescript
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label>姓名:</label>
<input formControlName="name" />
</div>
<div>
<label>邮箱:</label>
<input formControlName="email" />
</div>
<button type="submit" [disabled]="userForm.invalid">提交</button>
</form>
`
})
export class UserFormComponent {
userForm = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(2)]),
email: new FormControl('', [Validators.required, Validators.email])
});
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
4.2 FormGroup方法 #
typescript
// 获取值
const value = this.userForm.value;
// 设置值
this.userForm.setValue({
name: 'John',
email: 'john@example.com'
});
// 补丁更新(部分更新)
this.userForm.patchValue({
name: 'John'
});
// 重置
this.userForm.reset();
this.userForm.reset({ name: '', email: '' });
// 获取控件
const nameControl = this.userForm.get('name');
// 检查控件
this.userForm.contains('name');
// 添加/移除控件
this.userForm.addControl('age', new FormControl(''));
this.userForm.removeControl('age');
// 启用/禁用
this.userForm.enable();
this.userForm.disable();
五、FormBuilder #
5.1 使用FormBuilder #
typescript
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `...`
})
export class UserFormComponent {
constructor(private fb: FormBuilder) {}
userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
age: [null, [Validators.min(18), Validators.max(100)]],
address: this.fb.group({
province: [''],
city: [''],
detail: ['']
})
});
}
5.2 FormBuilder方法 #
typescript
// control
this.fb.control('初始值', Validators.required);
// group
this.fb.group({
name: ['', Validators.required],
email: ['', Validators.email]
});
// array
this.fb.array([
this.fb.control('项目1'),
this.fb.control('项目2')
]);
// 非空断言
this.fb.group({
name: this.fb.nonNullable.control('')
});
六、嵌套FormGroup #
6.1 创建嵌套表单 #
typescript
userForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
province: ['', Validators.required],
city: ['', Validators.required],
detail: ['']
})
});
6.2 模板绑定 #
html
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label>姓名:</label>
<input formControlName="name" />
</div>
<div>
<label>邮箱:</label>
<input formControlName="email" />
</div>
<!-- 嵌套FormGroup -->
<div formGroupName="address">
<h3>地址信息</h3>
<div>
<label>省份:</label>
<input formControlName="province" />
</div>
<div>
<label>城市:</label>
<input formControlName="city" />
</div>
<div>
<label>详细地址:</label>
<input formControlName="detail" />
</div>
</div>
<button type="submit">提交</button>
</form>
6.3 访问嵌套控件 #
typescript
// 获取嵌套控件
const provinceControl = this.userForm.get('address.province');
const addressGroup = this.userForm.get('address') as FormGroup;
七、FormArray #
7.1 创建FormArray #
typescript
userForm = this.fb.group({
name: ['', Validators.required],
emails: this.fb.array([
this.fb.control('', Validators.email)
]),
phones: this.fb.array([
this.fb.group({
type: ['mobile'],
number: ['', Validators.required]
})
])
});
get emails() {
return this.userForm.get('emails') as FormArray;
}
get phones() {
return this.userForm.get('phones') as FormArray;
}
7.2 模板绑定 #
html
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label>姓名:</label>
<input formControlName="name" />
</div>
<!-- FormArray - 简单控件 -->
<div>
<label>邮箱列表:</label>
<div formArrayName="emails">
<div *ngFor="let email of emails.controls; let i = index">
<input [formControlName]="i" />
<button type="button" (click)="removeEmail(i)">删除</button>
</div>
</div>
<button type="button" (click)="addEmail()">添加邮箱</button>
</div>
<!-- FormArray - FormGroup -->
<div>
<label>电话列表:</label>
<div formArrayName="phones">
<div *ngFor="let phone of phones.controls; let i = index" [formGroupName]="i">
<select formControlName="type">
<option value="mobile">手机</option>
<option value="home">家庭</option>
<option value="work">工作</option>
</select>
<input formControlName="number" placeholder="电话号码" />
<button type="button" (click)="removePhone(i)">删除</button>
</div>
</div>
<button type="button" (click)="addPhone()">添加电话</button>
</div>
<button type="submit">提交</button>
</form>
7.3 FormArray操作 #
typescript
// 添加控件
addEmail() {
this.emails.push(this.fb.control('', Validators.email));
}
addPhone() {
this.phones.push(this.fb.group({
type: ['mobile'],
number: ['', Validators.required]
}));
}
// 移除控件
removeEmail(index: number) {
this.emails.removeAt(index);
}
removePhone(index: number) {
this.phones.removeAt(index);
}
// 其他操作
// 在指定位置插入
this.emails.insert(0, this.fb.control(''));
// 清空
this.emails.clear();
// 获取长度
const length = this.emails.length;
八、表单验证 #
8.1 内置验证器 #
typescript
import { Validators } from '@angular/forms';
name: ['', [
Validators.required,
Validators.minLength(3),
Validators.maxLength(20)
]]
email: ['', [
Validators.required,
Validators.email
]]
age: [null, [
Validators.min(18),
Validators.max(100)
]]
phone: ['', [
Validators.required,
Validators.pattern(/^1[3-9]\d{9}$/)
]]
8.2 显示验证错误 #
html
<div>
<label>姓名:</label>
<input formControlName="name" />
<div *ngIf="nameControl.invalid && nameControl.touched">
<span *ngIf="nameControl.errors?.['required']">姓名是必填项</span>
<span *ngIf="nameControl.errors?.['minlength']">
姓名至少{{ nameControl.errors?.['minlength']?.requiredLength }}个字符
</span>
</div>
</div>
typescript
get nameControl() {
return this.userForm.get('name');
}
8.3 自定义验证器 #
同步验证器:
typescript
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// 函数式验证器
export function forbiddenNameValidator(name: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = name.test(control.value);
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
// 使用
name: ['', [
Validators.required,
forbiddenNameValidator(/admin/i)
]]
验证器类:
typescript
import { AbstractControl, Validator, NG_VALIDATORS } from '@angular/forms';
import { Directive, Input } from '@angular/core';
@Directive({
selector: '[appForbiddenName]',
providers: [{
provide: NG_VALIDATORS,
useExisting: ForbiddenNameDirective,
multi: true
}]
})
export class ForbiddenNameDirective implements Validator {
@Input('appForbiddenName') forbiddenName: string;
validate(control: AbstractControl): ValidationErrors | null {
if (control.value === this.forbiddenName) {
return { forbiddenName: { value: control.value } };
}
return null;
}
}
8.4 跨字段验证 #
typescript
import { FormGroup, ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
export function passwordMatchValidator(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const password = group.get('password');
const confirmPassword = group.get('confirmPassword');
if (!password || !confirmPassword) {
return null;
}
return password.value === confirmPassword.value
? null
: { passwordMismatch: true };
};
}
// 使用
userForm = this.fb.group({
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator() });
8.5 异步验证器 #
typescript
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
export function uniqueEmailValidator(userService: UserService): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (!control.value) {
return of(null);
}
return userService.checkEmailExists(control.value).pipe(
map(exists => exists ? { emailExists: true } : null),
catchError(() => of(null))
);
};
}
// 使用
email: ['',
[Validators.required, Validators.email],
[uniqueEmailValidator(this.userService)]
]
九、完整示例 #
typescript
import { Component, OnInit } from '@angular/core';
import {
FormBuilder,
FormGroup,
FormArray,
Validators,
ReactiveFormsModule
} from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<h2>用户注册</h2>
<!-- 基本信息 -->
<div class="form-section">
<h3>基本信息</h3>
<div class="form-group">
<label>用户名:</label>
<input formControlName="username" />
<div class="error" *ngIf="username?.invalid && username?.touched">
<span *ngIf="username?.errors?.['required']">用户名是必填项</span>
<span *ngIf="username?.errors?.['minlength']">用户名至少3个字符</span>
</div>
</div>
<div class="form-group">
<label>邮箱:</label>
<input formControlName="email" />
<div class="error" *ngIf="email?.invalid && email?.touched">
<span *ngIf="email?.errors?.['required']">邮箱是必填项</span>
<span *ngIf="email?.errors?.['email']">请输入有效的邮箱</span>
</div>
</div>
<div class="form-group">
<label>密码:</label>
<input type="password" formControlName="password" />
<div class="error" *ngIf="password?.invalid && password?.touched">
<span *ngIf="password?.errors?.['required']">密码是必填项</span>
<span *ngIf="password?.errors?.['minlength']">密码至少8个字符</span>
</div>
</div>
<div class="form-group">
<label>确认密码:</label>
<input type="password" formControlName="confirmPassword" />
<div class="error" *ngIf="userForm.errors?.['passwordMismatch'] && confirmPassword?.touched">
两次密码不一致
</div>
</div>
</div>
<!-- 地址信息 -->
<div class="form-section" formGroupName="address">
<h3>地址信息</h3>
<div class="form-group">
<label>省份:</label>
<input formControlName="province" />
</div>
<div class="form-group">
<label>城市:</label>
<input formControlName="city" />
</div>
<div class="form-group">
<label>详细地址:</label>
<input formControlName="detail" />
</div>
</div>
<!-- 电话列表 -->
<div class="form-section">
<h3>电话列表</h3>
<div formArrayName="phones">
<div *ngFor="let phone of phones.controls; let i = index" [formGroupName]="i">
<select formControlName="type">
<option value="mobile">手机</option>
<option value="home">家庭</option>
<option value="work">工作</option>
</select>
<input formControlName="number" placeholder="电话号码" />
<button type="button" (click)="removePhone(i)">删除</button>
</div>
</div>
<button type="button" (click)="addPhone()">添加电话</button>
</div>
<button type="submit" [disabled]="userForm.invalid">提交</button>
<button type="button" (click)="resetForm()">重置</button>
</form>
<pre>{{ userForm.value | json }}</pre>
`
})
export class UserFormComponent implements OnInit {
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.userForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: ['', Validators.required],
address: this.fb.group({
province: [''],
city: [''],
detail: ['']
}),
phones: this.fb.array([
this.fb.group({
type: ['mobile'],
number: ['', Validators.required]
})
])
}, { validators: this.passwordMatchValidator });
}
get username() { return this.userForm.get('username'); }
get email() { return this.userForm.get('email'); }
get password() { return this.userForm.get('password'); }
get confirmPassword() { return this.userForm.get('confirmPassword'); }
get phones() { return this.userForm.get('phones') as FormArray; }
passwordMatchValidator(group: FormGroup): { [key: string]: boolean } | null {
const password = group.get('password');
const confirmPassword = group.get('confirmPassword');
if (!password || !confirmPassword) return null;
return password.value === confirmPassword.value ? null : { passwordMismatch: true };
}
addPhone() {
this.phones.push(this.fb.group({
type: ['mobile'],
number: ['', Validators.required]
}));
}
removePhone(index: number) {
this.phones.removeAt(index);
}
onSubmit() {
if (this.userForm.valid) {
console.log('表单提交:', this.userForm.value);
}
}
resetForm() {
this.userForm.reset();
}
}
十、总结 #
| 概念 | 说明 |
|---|---|
FormControl |
单个表单控件 |
FormGroup |
表单控件组 |
FormArray |
动态表单控件数组 |
FormBuilder |
简化表单创建 |
Validators |
内置验证器 |
| 自定义验证器 | 自定义验证逻辑 |
| 异步验证器 | 异步验证逻辑 |
下一步:HttpClient基础
最后更新:2026-03-26