In Angular reactive forms, what is the difference between setValue() and patchValue() on a FormGroup, and when does each one throw?
answer
- strict versus lenient
- every key versus some keys
- extra keys from the API
- same thing on a FormControl
- neither one adds controls
basics
~10 ssetValue() requires a value for every child control and rejects unknown keys, throwing otherwise; patchValue() updates only the keys it receives and ignores the rest. On a single FormControl both behave the same.
solid answer
~40 s`setValue()` is strict: the object must match the group's structure exactly. A missing child throws "Must supply a value for form control with name: 'postcode'", and an extra key throws "Cannot find form control with name: 'id'"; disabled children still need a value because `setValue` takes the raw shape. `patchValue()` is lenient: it walks the keys you pass, updates matching controls, skips unknown keys and leaves unmentioned controls untouched. Neither method adds or removes controls, so patching a longer array than the `FormArray` ignores the extra items. On a `FormControl` the two are identical, because `patchValue` just calls `setValue`. Use `setValue` when you hold a complete value and want mistakes to fail loudly, and `patchValue` when loading partial data such as an API response with extra fields.
code
ts · 16 linesimport {FormControl, FormGroup} from '@angular/forms';
const address = new FormGroup({
street: new FormControl('', {nonNullable: true}),
city: new FormControl('', {nonNullable: true}),
postcode: new FormControl('', {nonNullable: true}),
});
const fromApi = {id: 42, street: 'Main St 1', city: 'Lyon'};
address.patchValue(fromApi); // street and city set, id ignored, postcode unchanged
// address.setValue(fromApi as any);
// throws: Must supply a value for form control with name: 'postcode'
address.setValue({street: fromApi.street, city: fromApi.city, postcode: '69001'}); // exact shape: OKgo deeper
Remember the rule: setValue needs every field and no extras, patchValue updates only what you pass.
Explain the exact failures, the raw shape including disabled controls, and why neither method adds controls to a group or array.
Choose deliberately when loading API data: map to the form's shape and keep setValue's loud failures where drift must be caught.
Treat form shape as a contract with the API layer and decide where mapping lives, so drift is caught in one place.
## Two ways to write a group's value Every `FormGroup` and `FormArray` offers two methods to write values into its children. They differ in how strictly the input must match the tree. | | `setValue(value)` | `patchValue(value)` | |---|---|---| | Must include every child | yes, including disabled ones | no | | Unknown keys | throws | ignored | | Unmentioned children | not allowed | left unchanged | | On a `FormControl` | sets the value | identical: it calls `setValue` | | Adds or removes controls | never | never | ## setValue(): strict by design `setValue()` first checks the input against the tree, then writes each child: 1. For every child in the group, the input must have a value, otherwise it throws **"Must supply a value for form control with name: 'postcode'"**. This includes **disabled** children: `setValue` takes the *raw* shape, the same one `getRawValue()` returns. 2. For every key in the input, a child must exist, otherwise it throws **"Cannot find form control with name: 'id'"**. 3. An empty group throws a "no form controls registered" error, a message aimed at template-driven forms whose controls are not registered yet. The strictness is the point: if the API or the form changes shape, `setValue` fails at the line that is out of date instead of silently leaving a field stale. ## patchValue(): lenient by design `patchValue()` iterates the keys **you pass**. For each key it finds a matching child and patches it; keys with no matching child are skipped; children you did not mention keep their current values. It recurses into nested groups and arrays, and treats a `null` or `undefined` sub-object as "nothing to patch". On a `FormArray`, `patchValue([a, b, c])` updates indexes that exist and **ignores extra items**; it will not grow the array. Adding rows is a separate operation. ## A shipping-address example The API returns `{id: 42, street: 'Main St 1', city: 'Lyon'}` for a form group with `street`, `city` and `postcode`: - `address.setValue(apiResult)` throws twice over: `postcode` is missing and `id` is unknown. - `address.patchValue(apiResult)` sets street and city, ignores `id`, and leaves `postcode` as it was. If you want strictness and the API has extra fields, map the response to the form's shape first, then call `setValue`. That keeps the loud failure if the form later gains a field the mapping forgot. ## Options both methods accept - **`emitEvent: false`** suppresses `valueChanges`, `statusChanges` and the `events` stream for this write, useful when loading data should not trigger "user changed something" logic. - **`onlySelf: true`** updates this control without recomputing its ancestors. Both methods emit one `valueChanges` per updated child and one for each ancestor, unless events are suppressed. Neither marks controls as dirty or touched; those flags describe user interaction, not programmatic writes. ## Mistakes seen in edit forms - Calling `setValue(apiResponse)` directly and discovering in production that the API added a field: the form throws on every load. - Calling `patchValue` with a response whose nested object is `null`: the nested group is silently left as it was, and stale data stays on screen. - Expecting `patchValue` to add rows to a `FormArray` to match a longer list from the server: extra items are ignored. - Loading data with events enabled, so "user edited" listeners fire on page load; pass `{emitEvent: false}` for loads. ## Choosing in practice - **Loading a record into an edit form**: `patchValue` after mapping, or `setValue` if you mapped to the exact shape and want strictness. - **Resetting to known values**: `reset(value)`, which also clears `dirty` and `touched`. - **Updating one field**: `form.controls.city.setValue('Lyon')` on the control itself.
- A group has a disabled country control. Why does setValue() still demand a country value?`setValue` validates against every child, enabled or not, because it takes the raw value shape that `getRawValue()` returns. Only the `value` property leaves disabled children out. Pass the current country, or use `patchValue` if you do not want to touch it.
- Does patchValue() mark the patched controls as dirty?No. `dirty` and `touched` record user interaction through the view, so programmatic writes with `setValue` or `patchValue` leave them unchanged. If a loaded record should count as edited, call `markAsDirty()` explicitly; if it should look pristine, `reset(value)` is the clearer tool.
setValue is like handing in a replacement copy of a paper form: every box must be filled and there must be no boxes the form does not have, or the clerk rejects it. patchValue is a correction slip: you list only the boxes to change, and anything the slip mentions that is not on the form is simply ignored.
saying these in an interview costs you the question
- patchValue() throws when the object has extra keys
- setValue() ignores disabled controls
- patchValue() on a FormArray adds missing items
- setValue() and patchValue() differ on a single FormControl
- Programmatic setValue() marks the control as dirty