Skip to content

Commit

Permalink
feat(forms): Unified Control State Change Events (#54579)
Browse files Browse the repository at this point in the history
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes #10887

PR Close #54579
  • Loading branch information
JeanMeche authored and thePunderWoman committed Apr 3, 2024
1 parent 2ff74dc commit 1c736dc
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 41 deletions.
50 changes: 49 additions & 1 deletion goldens/public-api/forms/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
}): void;
get enabled(): boolean;
readonly errors: ValidationErrors | null;
readonly events: Observable<ControlEvent<TValue>>;
get<P extends string | (readonly (string | number)[])>(path: P): AbstractControlGetProperty<TRawValue, P>> | null;
get<P extends string | Array<string | number>>(path: P): AbstractControlGetProperty<TRawValue, P>> | null;
getError(errorCode: string, path?: Array<string | number> | string): any;
Expand All @@ -49,22 +50,28 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
hasError(errorCode: string, path?: Array<string | number> | string): boolean;
hasValidator(validator: ValidatorFn): boolean;
get invalid(): boolean;
markAllAsTouched(): void;
markAllAsTouched(opts?: {
emitEvent?: boolean;
}): void;
markAsDirty(opts?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
markAsPending(opts?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
markAsPristine(opts?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
markAsTouched(opts?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
markAsUntouched(opts?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
get parent(): FormGroup | FormArray | null;
abstract patchValue(value: TValue, options?: Object): void;
Expand Down Expand Up @@ -185,6 +192,11 @@ export abstract class ControlContainer extends AbstractControlDirective {
get path(): string[] | null;
}

// @public
export abstract class ControlEvent<T = any> {
abstract readonly source: AbstractControl<unknown>;
}

// @public
export interface ControlValueAccessor {
registerOnChange(fn: any): void;
Expand Down Expand Up @@ -769,6 +781,15 @@ export class PatternValidator extends AbstractValidatorDirective {
static ɵfac: i0.ɵɵFactoryDeclaration<PatternValidator, never>;
}

// @public
export class PristineEvent extends ControlEvent {
constructor(pristine: boolean, source: AbstractControl);
// (undocumented)
readonly pristine: boolean;
// (undocumented)
readonly source: AbstractControl;
}

// @public
export class RadioControlValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor, OnDestroy, OnInit {
constructor(renderer: Renderer2, elementRef: ElementRef, _registry: RadioControlRegistry, _injector: Injector);
Expand Down Expand Up @@ -854,6 +875,24 @@ export class SelectMultipleControlValueAccessor extends BuiltInControlValueAcces
// @public
export type SetDisabledStateOption = 'whenDisabledForLegacyCode' | 'always';

// @public
export class StatusEvent extends ControlEvent {
constructor(status: FormControlStatus, source: AbstractControl);
// (undocumented)
readonly source: AbstractControl;
// (undocumented)
readonly status: FormControlStatus;
}

// @public
export class TouchedEvent extends ControlEvent {
constructor(touched: boolean, source: AbstractControl);
// (undocumented)
readonly source: AbstractControl;
// (undocumented)
readonly touched: boolean;
}

// @public
export type UntypedFormArray = FormArray<any>;

Expand Down Expand Up @@ -925,6 +964,15 @@ export class Validators {
static requiredTrue(control: AbstractControl): ValidationErrors | null;
}

// @public
export class ValueChangeEvent<T> extends ControlEvent<T> {
constructor(value: T, source: AbstractControl);
// (undocumented)
readonly source: AbstractControl;
// (undocumented)
readonly value: T;
}

// @public (undocumented)
export const VERSION: Version;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
{
"name": "ControlContainer"
},
{
"name": "ControlEvent"
},
{
"name": "DEFAULT_APP_ID"
},
Expand Down Expand Up @@ -467,6 +470,9 @@
{
"name": "PlatformRef"
},
{
"name": "PristineChangeEvent"
},
{
"name": "R3Injector"
},
Expand Down Expand Up @@ -536,6 +542,9 @@
{
"name": "SkipSelf"
},
{
"name": "StatusChangeEvent"
},
{
"name": "Subject"
},
Expand Down Expand Up @@ -572,6 +581,9 @@
{
"name": "TestabilityRegistry"
},
{
"name": "TouchedChangeEvent"
},
{
"name": "USE_VALUE"
},
Expand All @@ -584,6 +596,9 @@
{
"name": "Validators"
},
{
"name": "ValueChangeEvent"
},
{
"name": "ViewContainerRef"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
{
"name": "ControlContainer"
},
{
"name": "ControlEvent"
},
{
"name": "DEFAULT_APP_ID"
},
Expand Down Expand Up @@ -458,6 +461,9 @@
{
"name": "PlatformRef"
},
{
"name": "PristineChangeEvent"
},
{
"name": "R3Injector"
},
Expand Down Expand Up @@ -524,6 +530,9 @@
{
"name": "SkipSelf"
},
{
"name": "StatusChangeEvent"
},
{
"name": "Subject"
},
Expand Down Expand Up @@ -566,6 +575,9 @@
{
"name": "TestabilityRegistry"
},
{
"name": "TouchedChangeEvent"
},
{
"name": "USE_VALUE"
},
Expand All @@ -575,6 +587,9 @@
{
"name": "VE_ViewContainerRef"
},
{
"name": "ValueChangeEvent"
},
{
"name": "ViewContainerRef"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export {SelectMultipleControlValueAccessor, ɵNgSelectMultipleOption} from './di
export {SetDisabledStateOption} from './directives/shared';
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MaxValidator, MinLengthValidator, MinValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
export {ControlConfig, FormBuilder, NonNullableFormBuilder, UntypedFormBuilder, ɵElement} from './form_builder';
export {AbstractControl, AbstractControlOptions, FormControlStatus, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵRawValue, ɵTokenize, ɵTypedOrUntyped, ɵValue, ɵWriteable} from './model/abstract_model';
export {AbstractControl, AbstractControlOptions, ControlEvent, FormControlStatus, PristineChangeEvent as PristineEvent, StatusChangeEvent as StatusEvent, TouchedChangeEvent as TouchedEvent, ValueChangeEvent, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵRawValue, ɵTokenize, ɵTypedOrUntyped, ɵValue, ɵWriteable} from './model/abstract_model';
export {FormArray, isFormArray, UntypedFormArray, ɵFormArrayRawValue, ɵFormArrayValue} from './model/form_array';
export {FormControl, FormControlOptions, FormControlState, isFormControl, UntypedFormControl, ɵFormControlCtor} from './model/form_control';
export {FormGroup, FormRecord, isFormGroup, isFormRecord, UntypedFormGroup, ɵFormGroupRawValue, ɵFormGroupValue, ɵOptionalKeys} from './model/form_group';
Expand Down

0 comments on commit 1c736dc

Please sign in to comment.