---
title: "Reactive forms"
date: "2026-04"
category: "Angular"
tags: ["Angular"]
description: "The biggest difference between angular's design principles and react's is that in react, state is basically everything, while angular wraps every feature up for you..."
source: "https://enriquemark.com/en/posts/%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A1%A8%E5%8D%95"
---

The biggest difference between angular's design principles and react's is that in react, `state` is basically everything, while angular wraps every feature up for you. `signal` looks a lot like `state`, but in practice you rarely seem to touch `signal` directly. The official design principle is to package up the best practices, and most of the time the official tools are enough on their own. Not like react, which hands you the smallest pieces and lets you assemble them yourself. `signal` actually came later, under react's influence, but on the whole angular is still the big-and-complete kind, built mostly around pre-packaged tools.

Analogies are still a good way to carry knowledge over, though.

`FormControl`, start here. This one is a bit like react's `useState`. Its main job is to define a single state value, which in react you update by hand, but here that's all wrapped up for you.

```ts
//react style, you have to update with set yourself
const [username, setUsername] = useState('');
<input value={username} onChange={e => setUsername(e.target.value)} />

//angular style, updates automatically
usernameControl = new FormControl('');
<input [formControl]="usernameControl" />
```

Besides the first argument, which is the value, a `FormControl` object also takes one or more validator objects

```ts
//a single one, write it directly
username: new FormControl('', Validators.required)
//more than one, use a list
email: new FormControl('', [Validators.required, Validators.email]),
```

This is about the smallest object in a form. It has the properties below

|Property|Meaning|True when|
|---|---|---|
|`pristine`|original state|the user hasn't edited it|
|`dirty`|dirty data|the user has edited it|
|`untouched`|not touched|the user hasn't visited it (never focused)|
|`touched`|touched|the user has visited it (after blur)|
|`valid`|passes validation|no validation errors|
|`invalid`|fails validation|has validation errors|
|`disabled`|disabled|the control is turned off|
|`enabled`|enabled|the control is usable|

`FormGroup` is a wrapper over a pile of `FormControl`s, a `group` in the literal sense

```ts
//every one of them has to be declared explicitly
loginForm = new FormGroup({
  username: new FormControl(''),
  email: new FormControl(''),
  password: new FormControl('')
});
// reading the value
this.loginForm.value // { username: '', email: '', password: '' }
this.loginForm.get('username').value // ''
//in html you read the value through the attribute, then use formControlName for the matching key
<form [formGroup]="loginForm">
  <input formControlName="username" />
  <input formControlName="email" />
  <input formControlName="password" />
</form>
```

`FormBuilder` you can tell from the above too, using `FormGroup` directly is a real pain, so the official library gives you a factory class with everything wrapped up, one step and done.

```ts
// without FormBuilder
// fairly verbose
loginForm = new FormGroup({
  username: new FormControl('', Validators.required),
  email: new FormControl('', [Validators.required, Validators.email]),
});

// with FormBuilder
// done in one shot, this is also why the value is written as a list
// the logic here is no different from working with FormControl, value and validators (one or a list)
constructor(private fb: FormBuilder) {}
loginForm = this.fb.group({
  username: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
});
```

`Validators` is the validator. Plain react has no validators packaged up for you, you need an extra library, and without one you write your own hook. angular's team built one in, but you can also write your own.

```ts
// custom validator, takes a control object and returns an error object or null
function forbiddenName(control: AbstractControl) {
  return control.value === 'admin' ? { forbiddenName: true } : null;
}
// same as the built-in ones, put the validator function in the validator slot
username = new FormControl('', [Validators.required, forbiddenName]);
```

`valueChanges` is like react's side-effect function `useEffect`. It fires when the data you point it at changes, a side effect in the literal sense. The two do the same kind of thing here.

```ts
// react side-effect style
useEffect(() => {
  console.log('username changed:', username);
  // below is the data object it's bound to
}, [username]);

// below is how valueChanges does it, every Control object has this method
// and what it returns is an Observable, so you have to subscribe
// this one watches a single data object
this.usernameControl.valueChanges.subscribe(value => {
  console.log('username changed:', value);
});
// this one watches the whole form data object
this.loginForm.valueChanges.subscribe(formValue => {
  console.log('form changed:', formValue);
});
```

`FormArray` is the equivalent of an array state declared up front in react, except you can work on it with `array`-like methods, instead of the tedious business of copying with `...` and then updating the whole state every time.

```ts
// this part is annoying, plain react means writing the logic for updating the entire list every time
const [phones, setPhones] = useState(['']);
const addPhone = () => setPhones([...phones, '']);
const removePhone = (i) => setPhones(phones.filter((_, idx) => idx !== i));

//defining it is similar, but this one wants a FormControl object inside
phoneNumbers = new FormArray([
  new FormControl('')
]);
addPhone() {
//this call really is more convenient
  this.phoneNumbers.push(new FormControl(''));
}
removePhone(i: number) {
  this.phoneNumbers.removeAt(i);
}
```

---

**Translation note.** I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: [响应式表单](</zh-hant/posts/响应式表单>).
