Skip to content

Commit

Permalink
fixup! fix(forms): Make NgControlStatus host bindings OnPush comp…
Browse files Browse the repository at this point in the history
…atible
  • Loading branch information
atscott committed May 10, 2024
1 parent 20f5b99 commit 5e83ed8
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 43 deletions.
6 changes: 3 additions & 3 deletions goldens/public-api/forms/index.md
Expand Up @@ -76,7 +76,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
get parent(): FormGroup | FormArray | null;
abstract patchValue(value: TValue, options?: Object): void;
get pending(): boolean;
readonly pristine: boolean;
get pristine(): boolean;
removeAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void;
removeValidators(validators: ValidatorFn | ValidatorFn[]): void;
abstract reset(value?: TValue, options?: Object): void;
Expand All @@ -88,9 +88,9 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
setParent(parent: FormGroup | FormArray | null): void;
setValidators(validators: ValidatorFn | ValidatorFn[] | null): void;
abstract setValue(value: TRawValue, options?: Object): void;
readonly status: FormControlStatus;
get status(): FormControlStatus;
readonly statusChanges: Observable<FormControlStatus>;
readonly touched: boolean;
get touched(): boolean;
get untouched(): boolean;
get updateOn(): FormHooks;
updateValueAndValidity(opts?: {
Expand Down
15 changes: 9 additions & 6 deletions packages/forms/src/directives/ng_form.ts
Expand Up @@ -8,6 +8,7 @@

import {
AfterViewInit,
computed,
Directive,
EventEmitter,
forwardRef,
Expand All @@ -17,6 +18,7 @@ import {
Provider,
Self,
signal,
untracked,
ɵWritable as Writable,
} from '@angular/core';

Expand Down Expand Up @@ -127,9 +129,12 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
* @description
* Returns whether the form submission has been triggered.
*/
public readonly submitted: boolean = false;
get submitted(): boolean {
return untracked(this.submittedReactive);
}
/** @internal */
readonly _submitted = signal(false);
readonly _submitted = computed(() => this.submittedReactive);
private readonly submittedReactive = signal(false);

private _directives = new Set<NgModel>();

Expand Down Expand Up @@ -330,8 +335,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
* @param $event The "submit" event object
*/
onSubmit($event: Event): boolean {
(this as Writable<this>).submitted = true;
this._submitted.set(true);
this.submittedReactive.set(true);
syncPendingControls(this.form, this._directives);
this.ngSubmit.emit($event);
// Forms with `method="dialog"` have some special behavior
Expand All @@ -355,8 +359,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
*/
resetForm(value: any = undefined): void {
this.form.reset(value);
(this as Writable<this>).submitted = false;
this._submitted.set(false);
this.submittedReactive.set(false);
}

private _setUpdateStrategy() {
Expand Down
63 changes: 29 additions & 34 deletions packages/forms/src/model/abstract_model.ts
Expand Up @@ -12,6 +12,7 @@ import {
ɵRuntimeError as RuntimeError,
ɵWritable as Writable,
untracked,
computed,
} from '@angular/core';
import {Observable, Subject} from 'rxjs';

Expand Down Expand Up @@ -574,9 +575,12 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
* These status values are mutually exclusive, so a control cannot be
* both valid AND invalid or invalid AND disabled.
*/
public readonly status!: FormControlStatus;
get status(): FormControlStatus {
return untracked(this.statusReactive)!;
}
/** @internal */
readonly _status = signal<FormControlStatus | null>(null);
readonly _status = computed(() => this.statusReactive());
private readonly statusReactive = signal<FormControlStatus | undefined>(undefined);

/**
* A control is `valid` when its `status` is `VALID`.
Expand Down Expand Up @@ -655,10 +659,12 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
* @returns True if the user has not yet changed the value in the UI; compare `dirty`.
* Programmatic changes to a control's value do not mark it dirty.
*/
public readonly pristine: boolean = true;

get pristine(): boolean {
return untracked(this.pristineReactive);
}
/** @internal */
readonly _pristine = signal(true);
readonly _pristine = computed(() => this.pristineReactive());
private readonly pristineReactive = signal(true);

/**
* A control is `dirty` if the user has changed the value
Expand All @@ -677,10 +683,12 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
* A control is marked `touched` once the user has triggered
* a `blur` event on it.
*/
public readonly touched: boolean = false;

get touched(): boolean {
return untracked(this.touchedReactive);
}
/** @internal */
readonly _touched = signal(false);
readonly _touched = computed(() => this.touchedReactive());
private readonly touchedReactive = signal(false);

/**
* True if the control has not been marked as touched
Expand Down Expand Up @@ -946,8 +954,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
opts: {onlySelf?: boolean; emitEvent?: boolean; sourceControl?: AbstractControl} = {},
): void {
const changed = this.touched === false;
(this as Writable<this>).touched = true;
untracked(() => this._touched.set(this.touched));
untracked(() => this.touchedReactive.set(true));

const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
Expand Down Expand Up @@ -1007,8 +1014,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
opts: {onlySelf?: boolean; emitEvent?: boolean; sourceControl?: AbstractControl} = {},
): void {
const changed = this.touched === true;
(this as Writable<this>).touched = false;
untracked(() => this._touched.set(this.touched));
untracked(() => this.touchedReactive.set(false));
this._pendingTouched = false;

const sourceControl = opts.sourceControl ?? this;
Expand Down Expand Up @@ -1054,8 +1060,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
opts: {onlySelf?: boolean; emitEvent?: boolean; sourceControl?: AbstractControl} = {},
): void {
const changed = this.pristine === true;
(this as Writable<this>).pristine = false;
untracked(() => this._pristine.set(false));
untracked(() => this.pristineReactive.set(false));

const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
Expand Down Expand Up @@ -1099,8 +1104,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
opts: {onlySelf?: boolean; emitEvent?: boolean; sourceControl?: AbstractControl} = {},
): void {
const changed = this.pristine === false;
(this as Writable<this>).pristine = true;
untracked(() => this._pristine.set(true));
untracked(() => this.pristineReactive.set(true));
this._pendingDirty = false;

const sourceControl = opts.sourceControl ?? this;
Expand Down Expand Up @@ -1147,8 +1151,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
markAsPending(
opts: {onlySelf?: boolean; emitEvent?: boolean; sourceControl?: AbstractControl} = {},
): void {
(this as Writable<this>).status = PENDING;
untracked(() => this._status.set(PENDING));
untracked(() => this.statusReactive.set(PENDING));

const sourceControl = opts.sourceControl ?? this;
if (opts.emitEvent !== false) {
Expand Down Expand Up @@ -1190,8 +1193,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
// parent's dirtiness based on the children.
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);

(this as Writable<this>).status = DISABLED;
untracked(() => this._status.set(DISABLED));
untracked(() => this.statusReactive.set(DISABLED));
(this as Writable<this>).errors = null;
this._forEachChild((control: AbstractControl) => {
/** We don't propagate the source control downwards */
Expand Down Expand Up @@ -1234,8 +1236,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
// parent's dirtiness based on the children.
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);

(this as Writable<this>).status = VALID;
untracked(() => this._status.set(VALID));
untracked(() => this.statusReactive.set(VALID));
this._forEachChild((control: AbstractControl) => {
control.enable({...opts, onlySelf: true});
});
Expand Down Expand Up @@ -1322,8 +1323,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
if (this.enabled) {
this._cancelExistingSubscription();
(this as Writable<this>).errors = this._runValidator();
(this as Writable<this>).status = this._calculateStatus();
untracked(() => this._status.set(this.status));
untracked(() => this.statusReactive.set(this._calculateStatus()));

if (this.status === VALID || this.status === PENDING) {
this._runAsyncValidator(opts.emitEvent);
Expand All @@ -1350,8 +1350,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
}

private _setInitialStatus() {
(this as Writable<this>).status = this._allControlsDisabled() ? DISABLED : VALID;
untracked(() => this._status.set(this.status));
untracked(() => this.statusReactive.set(this._allControlsDisabled() ? DISABLED : VALID));
}

private _runValidator(): ValidationErrors | null {
Expand All @@ -1360,8 +1359,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T

private _runAsyncValidator(emitEvent?: boolean): void {
if (this.asyncValidator) {
(this as Writable<this>).status = PENDING;
untracked(() => this._status.set(PENDING));
untracked(() => this.statusReactive.set(PENDING));
this._hasOwnPendingAsyncValidator = true;
const obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription = obs.subscribe((errors: ValidationErrors | null) => {
Expand Down Expand Up @@ -1557,8 +1555,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T

/** @internal */
_updateControlsErrors(emitEvent: boolean, changedControl: AbstractControl): void {
(this as Writable<this>).status = this._calculateStatus();
untracked(() => this._status.set(this.status));
untracked(() => this.statusReactive.set(this._calculateStatus()));

if (emitEvent) {
(this.statusChanges as EventEmitter<FormControlStatus>).emit(this.status);
Expand Down Expand Up @@ -1618,8 +1615,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
_updatePristine(opts: {onlySelf?: boolean}, changedControl: AbstractControl): void {
const newPristine = !this._anyControlsDirty();
const changed = this.pristine !== newPristine;
(this as Writable<this>).pristine = newPristine;
untracked(() => this._pristine.set(newPristine));
untracked(() => this.pristineReactive.set(newPristine));

if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts, changedControl);
Expand All @@ -1632,8 +1628,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T

/** @internal */
_updateTouched(opts: {onlySelf?: boolean} = {}, changedControl: AbstractControl): void {
(this as Writable<this>).touched = this._anyControlsTouched();
untracked(() => this._touched.set(this.touched));
untracked(() => this.touchedReactive.set(this._anyControlsTouched()));
this._events.next(new TouchedChangeEvent(this.touched, changedControl));

if (this._parent && !opts.onlySelf) {
Expand Down

0 comments on commit 5e83ed8

Please sign in to comment.