EnriqueMark
Angular

响应式表单

2026-04 简体中文

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

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

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

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

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

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

//单个直接写
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

//每一个都要显示声明
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非常麻烦,所以官方提供了一个封装好的工厂类,直接一步到位。

// 不用 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这边官方就做了一个出来,但同时也可以自己实现。

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

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

// 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的方法来操作,而不必要每次都繁琐的用...复制再更新整个状态。

// 这里就很麻烦,原生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);
}