---
title: "响应式表单"
date: "2026-04"
category: "Angular"
tags: ["Angular"]
description: "angular的设计原则和react最大的差别是，后者基本是state就是一切，但前者会把各种功能包装好，signal虽然很像state，但实践中似乎很少直接操作signal..."
source: "https://enriquemark.com/zh-hans/posts/%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A1%A8%E5%8D%95"
---

angular的设计原则和react最大的差别是，后者基本是`state`就是一切，但前者会把各种功能包装好，`signal`虽然很像`state`，但实践中似乎很少直接操作`signal`。官方的设计原则就是把最佳实践封装好，大部分情况下只用官方封装好的工具就够了。而不是react那样给你最小的内容，自己去组装。`signal`反而是受了react的影响才后来推出，但总体上来说，angular还是大而全的，以封装好的工具为主。

不过用类比来做知识迁移还是很合适的。

`FormControl` 从这个开始。这个有点类似于react的`useState`，主要的作用是定义单个状态值，在react里需要手动更新，但这边都给封装好了。

```ts
//react写法，需要自己更新set
const [username, setUsername] = useState('');
<input value={username} onChange={e => setUsername(e.target.value)} />

//angular写法，自动更新
usernameControl = new FormControl('');
<input [formControl]="usernameControl" />
```

`FormControl`对象接受的元素，除了第一个是值外，还同时接受单个或者多个验证器对象

```ts
//单个直接写
username: new FormControl('', Validators.required)
//多个就用list
email: new FormControl('', [Validators.required, Validators.email]),
```

这算是form的最小对象了，有下面这些属性

|属性|含义|何时为 true|
|---|---|---|
|`pristine`|原始状态|用户未修改过|
|`dirty`|脏数据|用户已修改过|
|`untouched`|未触碰|用户未访问过（未 focus）|
|`touched`|已触碰|用户已访问过（blur 后）|
|`valid`|验证通过|无验证错误|
|`invalid`|验证失败|有验证错误|
|`disabled`|已禁用|控件被禁用|
|`enabled`|已启用|控件可用|

`FormGroup` 这个就是一堆 `FormControl` 的封装，就是字面意思的`group`

```ts
//每一个都要显示声明
loginForm = new FormGroup({
  username: new FormControl(''),
  email: new FormControl(''),
  password: new FormControl('')
});
// 取值
this.loginForm.value // { username: '', email: '', password: '' }
this.loginForm.get('username').value // ''
//html里面用属性取值，然后用formControlName取对应的key
<form [formGroup]="loginForm">
  <input formControlName="username" />
  <input formControlName="email" />
  <input formControlName="password" />
</form>
```

`FormBuilder` 上面也能看出来，直接用`FormGroup`非常麻烦，所以官方提供了一个封装好的工厂类，直接一步到位。

```ts
// 不用 FormBuilder
// 写法比较繁琐
loginForm = new FormGroup({
  username: new FormControl('', Validators.required),
  email: new FormControl('', [Validators.required, Validators.email]),
});

// 用 FormBuilder
// 一次性搞定，这也是为什么定义值的时候用的是list
// 这里的逻辑其实和操作 FormControl 时没差别，值和验证器（单个或list）
constructor(private fb: FormBuilder) {}
loginForm = this.fb.group({
  username: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
});
```

`Validators` 这个是验证器。原生的react里没有封装好的验证其，得用额外的库，没有就只能自己写hook。但angular这边官方就做了一个出来，但同时也可以自己实现。

```ts
// 自定义验证器，接受一个control对象，返回错误对象或null
function forbiddenName(control: AbstractControl) {
  return control.value === 'admin' ? { forbiddenName: true } : null;
}
// 和官方定义好的一样，将验证器函数放到验证器的位置
username = new FormControl('', [Validators.required, forbiddenName]);
```

`valueChanges` 类似react里的副作用`useEffect`函数，这个函数会在指定数据变动时触发，也就是字面意义上的副作用。这里两者的功能是类似的。

```ts
// react的副作用写法
useEffect(() => {
  console.log('username changed:', username);
  // 下面是绑定的数据对象
}, [username]);

// 下面是valueChanges的做法，任何Control对象都有这个方法
// 而其返回结果是一个Observable，所以需要订阅
// 这里是监听单个数据对象
this.usernameControl.valueChanges.subscribe(value => {
  console.log('username changed:', value);
});
// 这里是监听整个表单数据对象
this.loginForm.valueChanges.subscribe(formValue => {
  console.log('form changed:', formValue);
});
```

`FormArray` 这个相当于react里提前定义好的数组状态，但可以直接用类似`array`的方法来操作，而不必要每次都繁琐的用`...`复制再更新整个状态。

```ts
// 这里就很麻烦，原生react的写法相当于每次都要把更新整个列表的逻辑写进去
const [phones, setPhones] = useState(['']);
const addPhone = () => setPhones([...phones, '']);
const removePhone = (i) => setPhones(phones.filter((_, idx) => idx !== i));

//定义方法类似，但这里内部要求一个FormControl对象
phoneNumbers = new FormArray([
  new FormControl('')
]);
addPhone() {
//这个调用确实更方便
  this.phoneNumbers.push(new FormControl(''));
}
removePhone(i: number) {
  this.phoneNumbers.removeAt(i);
}
```
