From 5e83ed86f920857f89c7248a7186a04ad493625b Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Thu, 9 May 2024 17:57:05 -0700 Subject: [PATCH] fixup! fix(forms): Make `NgControlStatus` host bindings `OnPush` compatible --- goldens/public-api/forms/index.md | 6 +-- packages/forms/src/directives/ng_form.ts | 15 +++--- packages/forms/src/model/abstract_model.ts | 63 ++++++++++------------ 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/goldens/public-api/forms/index.md b/goldens/public-api/forms/index.md index 5049b7a9700f87..a81bc0ce652438 100644 --- a/goldens/public-api/forms/index.md +++ b/goldens/public-api/forms/index.md @@ -76,7 +76,7 @@ export abstract class AbstractControl; - readonly touched: boolean; + get touched(): boolean; get untouched(): boolean; get updateOn(): FormHooks; updateValueAndValidity(opts?: { diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 4d740c764b65fe..5531dd673ef518 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -8,6 +8,7 @@ import { AfterViewInit, + computed, Directive, EventEmitter, forwardRef, @@ -17,6 +18,7 @@ import { Provider, Self, signal, + untracked, ɵWritable as Writable, } from '@angular/core'; @@ -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(); @@ -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).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 @@ -355,8 +359,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit { */ resetForm(value: any = undefined): void { this.form.reset(value); - (this as Writable).submitted = false; - this._submitted.set(false); + this.submittedReactive.set(false); } private _setUpdateStrategy() { diff --git a/packages/forms/src/model/abstract_model.ts b/packages/forms/src/model/abstract_model.ts index 67d23337d387ba..f5d59ce2013047 100644 --- a/packages/forms/src/model/abstract_model.ts +++ b/packages/forms/src/model/abstract_model.ts @@ -12,6 +12,7 @@ import { ɵRuntimeError as RuntimeError, ɵWritable as Writable, untracked, + computed, } from '@angular/core'; import {Observable, Subject} from 'rxjs'; @@ -574,9 +575,12 @@ export abstract class AbstractControl(null); + readonly _status = computed(() => this.statusReactive()); + private readonly statusReactive = signal(undefined); /** * A control is `valid` when its `status` is `VALID`. @@ -655,10 +659,12 @@ export abstract class AbstractControl this.pristineReactive()); + private readonly pristineReactive = signal(true); /** * A control is `dirty` if the user has changed the value @@ -677,10 +683,12 @@ export abstract class AbstractControl this.touchedReactive()); + private readonly touchedReactive = signal(false); /** * True if the control has not been marked as touched @@ -946,8 +954,7 @@ export abstract class AbstractControl).touched = true; - untracked(() => this._touched.set(this.touched)); + untracked(() => this.touchedReactive.set(true)); const sourceControl = opts.sourceControl ?? this; if (this._parent && !opts.onlySelf) { @@ -1007,8 +1014,7 @@ export abstract class AbstractControl).touched = false; - untracked(() => this._touched.set(this.touched)); + untracked(() => this.touchedReactive.set(false)); this._pendingTouched = false; const sourceControl = opts.sourceControl ?? this; @@ -1054,8 +1060,7 @@ export abstract class AbstractControl).pristine = false; - untracked(() => this._pristine.set(false)); + untracked(() => this.pristineReactive.set(false)); const sourceControl = opts.sourceControl ?? this; if (this._parent && !opts.onlySelf) { @@ -1099,8 +1104,7 @@ export abstract class AbstractControl).pristine = true; - untracked(() => this._pristine.set(true)); + untracked(() => this.pristineReactive.set(true)); this._pendingDirty = false; const sourceControl = opts.sourceControl ?? this; @@ -1147,8 +1151,7 @@ export abstract class AbstractControl).status = PENDING; - untracked(() => this._status.set(PENDING)); + untracked(() => this.statusReactive.set(PENDING)); const sourceControl = opts.sourceControl ?? this; if (opts.emitEvent !== false) { @@ -1190,8 +1193,7 @@ export abstract class AbstractControl).status = DISABLED; - untracked(() => this._status.set(DISABLED)); + untracked(() => this.statusReactive.set(DISABLED)); (this as Writable).errors = null; this._forEachChild((control: AbstractControl) => { /** We don't propagate the source control downwards */ @@ -1234,8 +1236,7 @@ export abstract class AbstractControl).status = VALID; - untracked(() => this._status.set(VALID)); + untracked(() => this.statusReactive.set(VALID)); this._forEachChild((control: AbstractControl) => { control.enable({...opts, onlySelf: true}); }); @@ -1322,8 +1323,7 @@ export abstract class AbstractControl).errors = this._runValidator(); - (this as Writable).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); @@ -1350,8 +1350,7 @@ export abstract class AbstractControl).status = this._allControlsDisabled() ? DISABLED : VALID; - untracked(() => this._status.set(this.status)); + untracked(() => this.statusReactive.set(this._allControlsDisabled() ? DISABLED : VALID)); } private _runValidator(): ValidationErrors | null { @@ -1360,8 +1359,7 @@ export abstract class AbstractControl).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) => { @@ -1557,8 +1555,7 @@ export abstract class AbstractControl).status = this._calculateStatus(); - untracked(() => this._status.set(this.status)); + untracked(() => this.statusReactive.set(this._calculateStatus())); if (emitEvent) { (this.statusChanges as EventEmitter).emit(this.status); @@ -1618,8 +1615,7 @@ export abstract class AbstractControl).pristine = newPristine; - untracked(() => this._pristine.set(newPristine)); + untracked(() => this.pristineReactive.set(newPristine)); if (this._parent && !opts.onlySelf) { this._parent._updatePristine(opts, changedControl); @@ -1632,8 +1628,7 @@ export abstract class AbstractControl).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) {