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 22, 2024
1 parent f3a87cd commit d6fa9df
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 42 deletions.
8 changes: 4 additions & 4 deletions goldens/public-api/forms/index.md
Original file line number Diff line number Diff line change
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 Expand Up @@ -702,7 +702,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
setValue(value: {
[key: string]: any;
}): void;
readonly submitted: boolean;
get submitted(): boolean;
updateModel(dir: NgControl, value: any): void;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<NgForm, "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", ["ngForm"], { "options": { "alias": "ngFormOptions"; "required": false; }; }, { "ngSubmit": "ngSubmit"; }, never, never, false, never>;
Expand Down
15 changes: 9 additions & 6 deletions packages/forms/src/directives/ng_form.ts
Original file line number Diff line number Diff line change
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
65 changes: 33 additions & 32 deletions packages/forms/src/model/abstract_model.ts
Original file line number Diff line number Diff line change
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 @@ -590,9 +591,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 @@ -671,10 +675,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 @@ -693,10 +699,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 @@ -962,8 +970,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 @@ -1023,8 +1030,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 @@ -1070,8 +1076,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 @@ -1115,8 +1120,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 @@ -1163,8 +1167,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 @@ -1206,8 +1209,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 @@ -1250,8 +1252,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 @@ -1339,8 +1340,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
const shouldHaveEmitted = 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) {
// If the canceled subscription should have emitted
Expand Down Expand Up @@ -1369,8 +1369,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 @@ -1379,8 +1378,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T

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

/** @internal */
<<<<<<< HEAD
_updateControlsErrors(
emitEvent: boolean,
changedControl: AbstractControl,
shouldHaveEmitted?: boolean,
): void {
(this as Writable<this>).status = this._calculateStatus();
untracked(() => this._status.set(this.status));
=======
_updateControlsErrors(emitEvent: boolean, changedControl: AbstractControl): void {
untracked(() => this.statusReactive.set(this._calculateStatus()));
>>>>>>> 90a620a423 (fixup! fix(forms): Make `NgControlStatus` host bindings `OnPush` compatible)

if (emitEvent) {
(this.statusChanges as EventEmitter<FormControlStatus>).emit(this.status);
Expand Down Expand Up @@ -1663,8 +1666,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 @@ -1677,8 +1679,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 d6fa9df

Please sign in to comment.