Skip to content
 

Angular 表单

更新: 8/13/2026字数: 0 字 时长: 0 分钟

Angular 提供两种表单构建方式:模板驱动表单响应式表单(Reactive Forms)。两者都基于 ReactiveFormsModule / FormsModule 提供的表单控件模型。

一、两种表单对比

特性模板驱动表单响应式表单
数据模型由模板隐式创建在组件中显式创建(FormGroup/FormControl
逻辑位置主要写在模板中主要写在组件类中
适用场景简单表单、快速原型复杂表单、动态表单、复杂校验
可测试性较弱强,逻辑集中在类中易于测试
对应模块FormsModuleReactiveFormsModule

二、模板驱动表单

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 响应式,项目里怎么选?

  • 表单简单、逻辑少 → 模板驱动(代码更少)
  • 表单复杂、需要动态控件、复杂校验、强可测试性 → 响应式表单(更可控)