Skip to content

Commit a397ca3

Browse files
SkyZeroZxAndrewKushnir
authored andcommitted
docs: Add unified control state change events (angular#64279)
PR Close angular#64279
1 parent 3013226 commit a397ca3

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

adev/src/content/guide/forms/reactive-forms.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,108 @@ Initially, the form contains one `Alias` field. To add another field, click the
388388

389389
</docs-workflow>
390390

391+
## Unified control state change events
392+
393+
All form controls expose a single unified stream of **control state change events** through the `events` observable on `AbstractControl` (`FormControl`, `FormGroup`, `FormArray`, and `FormRecord`).
394+
This unified stream lets you react to **value**, **status**, **pristine**, **touched** and **reset** state changes and also for **form-level actions** such as **submit** , allowing you to handle all updates with a one subscription instead of wiring multiple observables.
395+
396+
### Event types
397+
398+
Each item emitted by `events` is an instance of a specific event class:
399+
400+
- **`ValueChangeEvent`** — when the control’s **value** changes.
401+
- **`StatusChangeEvent`** — when the control’s **validation status** updates to one of the `FormControlStatus` values (`VALID`, `INVALID`, `PENDING`, or `DISABLED`).
402+
- **`PristineChangeEvent`** — when the control’s **pristine/dirty** state changes.
403+
- **`TouchedChangeEvent`** — when the control’s **touched/untouched** state changes.
404+
- **`FormResetEvent`** — when a control or form is reset, either via the `reset()` API or a native action.
405+
- **`FormSubmittedEvent`** — when the form is submitted.
406+
407+
All event classes extend `ControlEvent` and include a `source` reference to the `AbstractControl` that originated the change, which is useful in large forms.
408+
409+
```ts
410+
import { Component } from '@angular/core';
411+
import {
412+
FormControl,
413+
ValueChangeEvent,
414+
StatusChangeEvent,
415+
PristineChangeEvent,
416+
TouchedChangeEvent,
417+
FormResetEvent,
418+
FormSubmittedEvent,
419+
ReactiveFormsModule,
420+
FormGroup,
421+
} from '@angular/forms';
422+
423+
@Component({/* ... */ })
424+
export class UnifiedEventsBasicComponent {
425+
form = new FormGroup({
426+
username: new FormControl(''),
427+
});
428+
429+
constructor() {
430+
this.form.events.subscribe((e) => {
431+
if (e instanceof ValueChangeEvent) {
432+
console.log('Value changed to: ', e.value);
433+
}
434+
435+
if (e instanceof StatusChangeEvent) {
436+
console.log('Status changed to: ', e.status);
437+
}
438+
439+
if (e instanceof PristineChangeEvent) {
440+
console.log('Pristine status changed to: ', e.pristine);
441+
}
442+
443+
if (e instanceof TouchedChangeEvent) {
444+
console.log('Touched status changed to: ', e.touched);
445+
}
446+
447+
if (e instanceof FormResetEvent) {
448+
console.log('Form was reset');
449+
}
450+
451+
if (e instanceof FormSubmittedEvent) {
452+
console.log('Form was submitted');
453+
}
454+
});
455+
}
456+
}
457+
```
458+
459+
### Filtering specific events
460+
461+
Prefer RxJS operators when you only need a subset of event types.
462+
463+
```ts
464+
import { filter } from 'rxjs/operators';
465+
import { StatusChangeEvent } from '@angular/forms';
466+
467+
control.events
468+
.pipe(filter((e) => e instanceof StatusChangeEvent))
469+
.subscribe((e) => console.log('Status:', e.status));
470+
```
471+
472+
### Unifying from multiple subscriptions
473+
474+
**Before**
475+
476+
```ts
477+
import { combineLatest } from 'rxjs/operators';
478+
479+
combineLatest([control.valueChanges, control.statusChanges])
480+
.subscribe(([value, status]) => { /* ... */ });
481+
```
482+
483+
**After**
484+
485+
```ts
486+
control.events.subscribe((e) => {
487+
// Handle ValueChangeEvent, StatusChangeEvent, etc.
488+
});
489+
```
490+
491+
NOTE: On value change, the emit happens right after a value of this control is updated. The value of a parent control (for example if this FormControl is a part of a FormGroup) is updated later, so accessing a value of a parent control (using the `value` property) from the callback of this event might result in getting a value that has not been updated yet. Subscribe to the `events` of the parent control instead.
492+
391493
## Reactive forms API summary
392494

393495
The following table lists the base classes and services used to create and manage reactive form controls.

packages/forms/src/model/abstract_model.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ export abstract class ControlEvent<T = any> {
101101
/**
102102
* Event fired when the value of a control changes.
103103
*
104+
* @see {@link AbstractControl.events}
105+
*
104106
* @publicApi
105107
*/
106108
export class ValueChangeEvent<T> extends ControlEvent<T> {
@@ -115,6 +117,8 @@ export class ValueChangeEvent<T> extends ControlEvent<T> {
115117
/**
116118
* Event fired when the control's pristine state changes (pristine <=> dirty).
117119
*
120+
* @see {@link AbstractControl.events}
121+
*
118122
* @publicApi */
119123
export class PristineChangeEvent extends ControlEvent {
120124
constructor(
@@ -128,6 +132,8 @@ export class PristineChangeEvent extends ControlEvent {
128132
/**
129133
* Event fired when the control's touched status changes (touched <=> untouched).
130134
*
135+
* @see {@link AbstractControl.events}
136+
*
131137
* @publicApi
132138
*/
133139
export class TouchedChangeEvent extends ControlEvent {
@@ -142,6 +148,8 @@ export class TouchedChangeEvent extends ControlEvent {
142148
/**
143149
* Event fired when the control's status changes.
144150
*
151+
* @see {@link AbstractControl.events}
152+
*
145153
* @publicApi
146154
*/
147155
export class StatusChangeEvent extends ControlEvent {
@@ -156,6 +164,8 @@ export class StatusChangeEvent extends ControlEvent {
156164
/**
157165
* Event fired when a form is submitted
158166
*
167+
* @see {@link AbstractControl.events}
168+
*
159169
* @publicApi
160170
*/
161171
export class FormSubmittedEvent extends ControlEvent {
@@ -166,6 +176,8 @@ export class FormSubmittedEvent extends ControlEvent {
166176
/**
167177
* Event fired when a form is reset.
168178
*
179+
* @see {@link AbstractControl.events}
180+
*
169181
* @publicApi
170182
*/
171183
export class FormResetEvent extends ControlEvent {
@@ -750,6 +762,7 @@ export abstract class AbstractControl<
750762
* `events` of the parent control instead.
751763
* For other event types, the events are emitted after the parent control has been updated.
752764
*
765+
* @see [Unified control state change events](guide/forms/reactive-forms#unified-control-state-change-events)
753766
*/
754767
public readonly events = this._events.asObservable();
755768

0 commit comments

Comments
 (0)