07_核心语法:表单组件
核心语法:表单组件
表单是Web应用中用于收集和处理用户输入的核心组件,Angular提供了两种强大的表单处理方案:模板驱动表单和响应式表单。模板驱动表单适合简单场景,通过模板指令管理表单状态;响应式表单则通过代码控制表单逻辑,更适合复杂场景。本章将详细讲解这两种表单的实现方式、验证机制及实战技巧。
1. 模板驱动表单:基于模板的表单处理
模板驱动表单通过Angular内置指令(如ngModel)在模板中定义表单逻辑,适合结构简单、验证规则较少的场景。其核心特点是"声明式"——表单状态由模板自动管理。
1.1 基础用法与双向绑定
使用模板驱动表单需先导入FormsModule:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-simple-form',
standalone: true,
imports: [FormsModule], // 导入FormsModule以使用模板驱动表单
template: `
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm.value)">
<div>
<label>姓名:</label>
<input
type="text"
name="username"
ngModel
required
#username="ngModel"
>
<!-- 错误提示 -->
@if (username.invalid && username.touched) {
<span class="error">姓名为必填项</span>
}
</div>
<div>
<label>邮箱:</label>
<input
type="email"
name="email"
ngModel
required
email
#email="ngModel"
>
@if (email.invalid && email.touched) {
@if (email.errors?.['required']) {
<span class="error">邮箱为必填项</span>
}
@if (email.errors?.['email']) {
<span class="error">请输入有效的邮箱格式</span>
}
}
</div>
<button type="submit" [disabled]="userForm.invalid">提交</button>
</form>
`
})
export class SimpleFormComponent {
onSubmit(formValue: any) {
console.log('表单提交:', formValue);
// 发送表单数据到服务器
}
}
核心知识点:
#userForm="ngForm":获取表单引用,用于访问表单状态(如invalid、valid)ngModel:实现双向绑定,将输入值与表单模型关联name属性:必填,用于标识表单控件,作为表单值对象的键#username="ngModel":获取单个控件的引用,用于访问控件状态(如touched、errors)
1.2 表单控件状态
模板驱动表单的控件拥有丰富的状态属性,用于动态控制UI表现:
| 状态属性 | 含义 |
|---|---|
valid |
控件验证通过 |
invalid |
控件验证失败 |
touched |
控件被用户交互过(失去焦点) |
untouched |
控件未被用户交互过 |
pristine |
控件值未被修改过 |
dirty |
控件值已被修改 |
pending |
异步验证正在进行 |
示例:根据状态动态添加样式
<style>
input.ng-valid.ng-touched {
border: 2px solid green;
}
input.ng-invalid.ng-touched {
border: 2px solid red;
}
.error {
color: red;
font-size: 0.8em;
}
</style>
1.3 自定义验证器
除了required、email等内置验证器,可通过指令创建自定义验证器:
import { Directive } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
// 自定义密码强度验证器
@Directive({
selector: '[appPasswordStrength]',
standalone: true,
providers: [{
provide: NG_VALIDATORS,
useExisting: PasswordStrengthDirective,
multi: true
}]
})
export class PasswordStrengthDirective implements Validator {
validate(control: AbstractControl): ValidationErrors | null {
const value = control.value as string;
if (!value) return null; // 空值由required验证器处理
// 验证规则:至少8位,包含数字和字母
const hasNumber = /\d/.test(value);
const hasLetter = /[a-zA-Z]/.test(value);
const isValidLength = value.length >= 8;
const isValid = hasNumber && hasLetter && isValidLength;
return isValid ? null : {
passwordStrength: {
message: '密码至少8位,需包含数字和字母'
}
};
}
}
使用自定义验证器:
<div>
<label>密码:</label>
<input
type="password"
name="password"
ngModel
required
appPasswordStrength
#password="ngModel"
>
@if (password.invalid && password.touched) {
@if (password.errors?.['required']) {
<span class="error">密码为必填项</span>
}
@if (password.errors?.['passwordStrength']) {
<span class="error">{{ password.errors['passwordStrength'].message }}</span>
}
}
</div>
2. 响应式表单:基于代码的表单控制
响应式表单通过TypeScript代码创建和管理表单控件,表单状态是"可编程的",适合复杂场景(如动态表单、复杂验证、表单状态监听)。Angular v20推荐结合Signals使用响应式表单,实现更高效的状态管理。
2.1 基础用法与FormGroup
使用响应式表单需导入ReactiveFormsModule:
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
standalone: true,
imports: [ReactiveFormsModule], // 导入响应式表单模块
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div>
<label>用户名:</label>
<input
type="text"
formControlName="username"
>
@if (usernameControl.invalid && usernameControl.touched) {
<span class="error">用户名不能为空</span>
}
</div>
<div>
<label>密码:</label>
<input
type="password"
formControlName="password"
>
@if (passwordControl.invalid && passwordControl.touched) {
@if (passwordControl.errors?.['required']) {
<span class="error">密码不能为空</span>
}
@if (passwordControl.errors?.['minlength']) {
<span class="error">密码至少6位</span>
}
}
</div>
<button type="submit" [disabled]="loginForm.invalid">登录</button>
</form>
`
})
export class ReactiveFormComponent {
// 创建表单组
loginForm = new FormGroup({
username: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required, Validators.minLength(6)])
});
// 获取单个控件的引用(简化模板访问)
get usernameControl() {
return this.loginForm.get('username')!;
}
get passwordControl() {
return this.loginForm.get('password')!;
}
onSubmit() {
if (this.loginForm.valid) {
console.log('表单提交:', this.loginForm.value);
// 处理登录逻辑
} else {
// 触发表单验证
this.markAllAsTouched();
}
}
// 手动标记所有控件为touched,触发错误提示
private markAllAsTouched() {
Object.values(this.loginForm.controls).forEach(control => {
control.markAsTouched();
});
}
}
核心类说明:
FormControl:管理单个表单控件的值和状态FormGroup:管理一组FormControl,聚合它们的状态Validators:内置验证器集合(required、minLength等)
2.2 与Signals结合(v20推荐)
Angular v20支持将表单状态转换为Signals,实现更高效的响应式更新:
import { Component, signal, computed } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators, toSignal } from '@angular/forms';
@Component({
selector: 'app-signal-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="searchForm" (ngSubmit)="onSearch()">
<input
type="text"
formControlName="query"
placeholder="搜索..."
>
<button type="submit" [disabled]="isFormInvalid()">搜索</button>
<p>搜索历史:{{ recentQueries().join(', ') }}</p>
</form>
`
})
export class SignalFormComponent {
searchForm = new FormGroup({
query: new FormControl('', [Validators.required, Validators.minLength(2)])
});
// 将表单状态转换为Signal
formValue = toSignal(this.searchForm.valueChanges, { initialValue: { query: '' } });
formStatus = toSignal(this.searchForm.statusChanges, { initialValue: 'INVALID' });
// 计算属性:最近的搜索记录
recentQueries = signal<string[]>([]);
// 计算属性:表单是否无效
isFormInvalid = computed(() => {
return this.formStatus() === 'INVALID';
});
onSearch() {
const query = this.formValue()?.query;
if (query) {
console.log('搜索:', query);
// 更新搜索历史
this.recentQueries.update(queries => [query, ...queries.slice(0, 4)]);
// 重置表单
this.searchForm.reset();
}
}
}
toSignal()将表单的valueChanges和statusChanges(Observable)转换为Signals,结合computed()可创建依赖表单状态的响应式计算属性。
2.3 动态表单:动态添加/移除控件
响应式表单支持动态调整表单结构,适合需要动态增减字段的场景(如多选项表单):
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, FormArray } from '@angular/forms';
@Component({
selector: 'app-dynamic-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="surveyForm">
<h3>兴趣爱好</h3>
<!-- 动态表单数组 -->
<div formArrayName="hobbies">
@for (hobby of hobbies.controls; track $index; let i = $index) {
<div>
<input
type="text"
[formControlName]="i"
placeholder="请输入兴趣爱好"
>
<button type="button" (click)="removeHobby(i)" [disabled]="hobbies.length <= 1">
删除
</button>
</div>
}
</div>
<button type="button" (click)="addHobby()">添加兴趣爱好</button>
<button type="button" (click)="onSave()">保存</button>
</form>
`
})
export class DynamicFormComponent {
// 包含动态表单数组的表单组
surveyForm = new FormGroup({
hobbies: new FormArray([
new FormControl('') // 初始一个控件
])
});
// 获取表单数组引用
get hobbies() {
return this.surveyForm.get('hobbies') as FormArray;
}
// 添加新控件
addHobby() {
this.hobbies.push(new FormControl(''));
}
// 移除指定索引的控件
removeHobby(index: number) {
this.hobbies.removeAt(index);
}
onSave() {
console.log('兴趣爱好:', this.hobbies.value);
}
}
FormArray用于管理动态数量的FormControl,通过push()和removeAt()方法动态调整控件数量。
2.4 异步验证器
对于需要后端验证的场景(如检查用户名是否已存在),可使用异步验证器:
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { delay, map } from 'rxjs/operators';
@Component({
selector: 'app-async-validation',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<div>
<label>用户名:</label>
<input [formControl]="usernameControl">
@if (usernameControl.pending) {
<span>检查用户名可用性...</span>
}
@if (usernameControl.invalid && usernameControl.touched) {
@if (usernameControl.errors?.['required']) {
<span class="error">用户名不能为空</span>
}
@if (usernameControl.errors?.['usernameTaken']) {
<span class="error">用户名已被占用</span>
}
}
</div>
`
})
export class AsyncValidationComponent {
usernameControl = new FormControl('',
[Validators.required], // 同步验证器
[this.checkUsernameAvailability.bind(this)] // 异步验证器
);
constructor(private http: HttpClient) {}
// 异步验证器:检查用户名是否已存在
checkUsernameAvailability(control: AbstractControl) {
return this.http.get<{ available: boolean }>(`/api/check-username?name=${control.value}`)
.pipe(
delay(1000), // 模拟网络延迟
map(response => {
// 验证失败返回错误对象,成功返回null
return response.available ? null : { usernameTaken: true };
})
);
}
}
异步验证器返回Observable,当服务器返回验证结果后,表单会自动更新状态。
3. 表单提交与数据处理
无论使用哪种表单类型,都需要处理表单提交、数据转换和错误处理等通用场景。
3.1 表单提交最佳实践
- 禁用无效提交:通过
[disabled]="form.invalid"防止用户提交无效表单 - 手动触发表单验证:提交前检查表单状态,对无效表单标记所有控件为
touched - 处理提交状态:添加加载状态防止重复提交
onSubmit() {
if (this.form.invalid) {
this.markAllAsTouched();
return;
}
this.isSubmitting = true;
this.apiService.submitData(this.form.value)
.subscribe({
next: () => {
this.isSubmitting = false;
alert('提交成功');
this.form.reset();
},
error: () => {
this.isSubmitting = false;
alert('提交失败,请重试');
}
});
}
3.2 表单数据转换
使用valueChanges监听表单数据变化,或通过patchValue()/setValue()更新表单数据:
// 监听数据变化
this.userForm.valueChanges.subscribe(value => {
console.log('表单数据变化:', value);
});
// 部分更新表单数据
this.userForm.patchValue({
username: '默认用户名'
});
// 完全替换表单数据(需匹配所有字段)
this.userForm.setValue({
username: '新用户名',
email: 'new@example.com'
});
4. 两种表单方案的对比与选型
| 特性 | 模板驱动表单 | 响应式表单 |
|---|---|---|
| 核心思想 | 声明式,依赖模板 | 命令式,依赖代码 |
| 状态管理 | 模板自动管理 | 手动代码控制 |
| 灵活性 | 适合简单场景 | 适合复杂场景 |
| 测试性 | 难以单元测试 | 易于单元测试 |
| 动态表单 | 实现复杂 | 原生支持 |
| 与Signals集成 | 有限 | 良好 |
选型建议:
- 简单表单(如登录、注册):优先使用模板驱动表单
- 复杂表单(如数据录入、动态表单):使用响应式表单
- 需要频繁监听或修改表单状态:使用响应式表单+Signals
通过本章学习,你已掌握Angular表单的核心用法,能够根据实际场景选择合适的表单方案,实现高效、可维护的用户输入处理逻辑。

浙公网安备 33010602011771号