Angular 表单
更新: 8/13/2026字数: 0 字 时长: 0 分钟
Angular 提供两种表单构建方式:模板驱动表单和响应式表单(Reactive Forms)。两者都基于 ReactiveFormsModule / FormsModule 提供的表单控件模型。
一、两种表单对比
| 特性 | 模板驱动表单 | 响应式表单 |
|---|---|---|
| 数据模型 | 由模板隐式创建 | 在组件中显式创建(FormGroup/FormControl) |
| 逻辑位置 | 主要写在模板中 | 主要写在组件类中 |
| 适用场景 | 简单表单、快速原型 | 复杂表单、动态表单、复杂校验 |
| 可测试性 | 较弱 | 强,逻辑集中在类中易于测试 |
| 对应模块 | FormsModule | ReactiveFormsModule |
二、模板驱动表单
1. 基本使用
导入 FormsModule 后,用 ngModel 实现双向绑定:
typescript
import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
@Component({
selector: "app-login",
standalone: true,
imports: [FormsModule],
templateUrl: "./login.component.html"
})
export class LoginComponent {
username = "";
password = "";
onSubmit() {
console.log(this.username, this.password);
}
}html
<form #loginForm="ngForm" (ngSubmit)="onSubmit()">
<input name="username" [(ngModel)]="username" required />
<input name="password" type="password" [(ngModel)]="password" required />
<button type="submit" [disabled]="!loginForm.valid">登录</button>
</form>注意:
ngModel必须配合name属性使用,否则无法注册到表单控件。
2. 模板驱动表单的校验
html
<input name="username" [(ngModel)]="username" required minlength="3" #nameRef="ngModel" />
<!-- 显示校验错误 -->
<div *ngIf="nameRef.invalid && nameRef.touched">用户名至少 3 个字符</div>三、响应式表单(Reactive Forms)
1. 核心类
| 类 | 说明 |
|---|---|
FormControl | 单个控件的值、状态、校验 |
FormGroup | 一组控件的集合,管理整体值、状态 |
FormArray | 动态长度的控件数组 |
FormBuilder | 简化的创建方式(语法糖) |
Validators | 内置校验器集合 |
2. 创建 FormGroup
typescript
import { Component } from "@angular/core";
import { ReactiveFormsModule, FormGroup, FormControl, Validators, FormBuilder } from "@angular/forms";
@Component({
selector: "app-register",
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: "./register.component.html"
})
export class RegisterComponent {
// 方式一:手动 new
registerForm = new FormGroup({
username: new FormControl("", [Validators.required, Validators.minLength(3)]),
email: new FormControl("", [Validators.required, Validators.email])
});
// 方式二:使用 FormBuilder(更简洁)
constructor(private fb: FormBuilder) {}
fbForm = this.fb.group({
username: ["", [Validators.required, Validators.minLength(3)]],
email: ["", [Validators.required, Validators.email]]
});
onSubmit() {
console.log(this.registerForm.value);
}
}3. 模板绑定
html
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<input formControlName="username" />
<div *ngIf="registerForm.get('username')?.invalid && registerForm.get('username')?.touched">
用户名至少 3 个字符
</div>
<input formControlName="email" />
<div *ngIf="registerForm.get('email')?.invalid">邮箱格式不正确</div>
<button type="submit" [disabled]="registerForm.invalid">注册</button>
</form>四、自定义校验器
1. 同步校验器
typescript
import { AbstractControl, ValidationErrors } from "@angular/forms";
// 校验手机号
export function phoneValidator(control: AbstractControl): ValidationErrors | null {
const valid = /^1[3-9]\d{9}$/.test(control.value);
return valid ? null : { phone: true };
}
// 使用
phone: ["", [Validators.required, phoneValidator]]2. 交叉字段校验(确认密码)
校验器作用于 FormGroup,比较两个控件的值:
typescript
export function passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const password = group.get("password")?.value;
const confirm = group.get("confirmPassword")?.value;
return password === confirm ? null : { mismatch: true };
}
this.registerForm = this.fb.group(
{
password: ["", Validators.required],
confirmPassword: ["", Validators.required]
},
{ validators: passwordMatchValidator } // 作用于整个 group
);3. 异步校验器(模拟接口校验用户名)
typescript
import { AbstractControl, ValidationErrors } from "@angular/forms";
import { Observable, of } from "rxjs";
import { delay, map } from "rxjs/operators";
export function usernameAsyncValidator(control: AbstractControl): Observable<ValidationErrors | null> {
return of(control.value).pipe(
delay(500), // 模拟网络请求
map((name) => (name === "admin" ? { taken: true } : null))
);
}
// 使用:第三个参数位置放异步校验器
username: ["", [Validators.required], [usernameAsyncValidator]]五、FormArray 动态表单
用于动态增删的列表,例如添加多个联系电话:
typescript
import { Component } from "@angular/core";
import { FormGroup, FormArray, FormBuilder, Validators } from "@angular/forms";
@Component({
selector: "app-contacts",
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: "./contacts.component.html"
})
export class ContactsComponent {
// 声明字段,避免「赋值给未声明属性」
contactsForm: FormGroup;
constructor(private fb: FormBuilder) {
this.contactsForm = this.fb.group({
contacts: this.fb.array([])
});
}
// 便捷访问 FormArray
get contacts(): FormArray {
return this.contactsForm.get("contacts") as FormArray;
}
addContact() {
// FormArray.push 会原地追加,直接调用即可
this.contacts.push(this.fb.control("", Validators.required));
}
removeContact(index: number) {
this.contacts.removeAt(index);
}
}html
<!-- 注意:FormArray 内放的是 FormControl,应直接绑定 [formControl] -->
<div formArrayName="contacts">
<div *ngFor="let contact of contacts.controls; let i = index">
<input [formControl]="contact" />
<button type="button" (click)="removeContact(i)">删除</button>
</div>
</div>
<button type="button" (click)="addContact()">添加联系方式</button>说明:当
FormArray内部是FormControl时,直接对遍历出的控件使用[formControl]="contact";只有内部是FormGroup时才用[formGroupName]="i"+formControlName。
六、常见问题解答
Q1:表单控件有哪些状态?
valid/invalid:是否通过校验dirty/pristine:是否被修改过touched/untouched:是否失焦过- 通常用
invalid && touched来「在用户交互后才显示错误」
Q2:如何给表单设置初始值或动态赋值?
- 初始化:
new FormControl("默认值")或fb.control("默认值") - 动态赋值:
setValue(必须提供完整值)/patchValue(可部分更新)
Q3:如何监听表单值变化?
typescript
this.registerForm.get("email")?.valueChanges.subscribe((value) => {
console.log("邮箱变化:", value);
});Q4:模板驱动 vs 响应式,项目里怎么选?
- 表单简单、逻辑少 → 模板驱动(代码更少)
- 表单复杂、需要动态控件、复杂校验、强可测试性 → 响应式表单(更可控)