---
title: "響應式表單"
date: "2026-04"
category: "Angular"
tags: ["Angular"]
description: "angular的設計原則和react最大的差別是，後者基本是state就是一切，但前者會把各種功能包裝好，signal雖然很像state，但實踐中似乎很少直接操作signal..."
source: "https://enriquemark.com/zh-hant/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);
}
```
