refactor(forms): use optional chaining for safer method calls in form directives

Simplifies null checks by leveraging optional chaining when invoking
optional callbacks

(cherry picked from commit 8b3b069be7)
This commit is contained in:
SkyZeroZx
2026-02-08 11:47:54 -05:00
committed by Andrew Kushnir
parent ae3c8fbc38
commit 2c3f3cc336
10 changed files with 46 additions and 62 deletions
@@ -264,7 +264,7 @@ export abstract class AbstractControlDirective {
* Resets the control with the provided value if the control is present.
*/
reset(value: any = undefined): void {
if (this.control) this.control.reset(value);
this.control?.reset(value);
}
/**
@@ -41,10 +41,8 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
/** @docs-private */
ngOnDestroy(): void {
if (this.formDirective) {
// Remove the group from its parent group.
this.formDirective.removeFormGroup(this);
}
// Remove the group from its parent group.
this.formDirective?.removeFormGroup(this);
}
/**
+4 -6
View File
@@ -256,9 +256,9 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
removeControl(dir: NgModel): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
if (container) {
container.removeControl(dir.name);
}
container?.removeControl(dir.name);
this._directives.delete(dir);
});
}
@@ -288,9 +288,7 @@ export class NgForm extends ControlContainer implements Form, AfterViewInit {
removeFormGroup(dir: NgModelGroup): void {
resolvedPromise.then(() => {
const container = this._findContainer(dir.path);
if (container) {
container.removeControl(dir.name);
}
container?.removeControl?.(dir.name);
});
}
+1 -1
View File
@@ -278,7 +278,7 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy {
/** @docs-private */
ngOnDestroy(): void {
this.formDirective && this.formDirective.removeControl(this);
this.formDirective?.removeControl(this);
}
/**
@@ -357,24 +357,20 @@ export abstract class AbstractFormDirective
}
private _cleanUpFormContainer(dir: FormArrayName | FormGroupName): void {
if (this.form) {
const ctrl: any = this.form.get(dir.path);
if (ctrl) {
const isControlUpdated = cleanUpFormContainer(ctrl, dir);
if (isControlUpdated) {
// Run validity check only in case a control was updated (i.e. view validators were
// removed) as removing view validators might cause validity to change.
ctrl.updateValueAndValidity({emitEvent: false});
}
const ctrl: any = this.form?.get(dir.path);
if (ctrl) {
const isControlUpdated = cleanUpFormContainer(ctrl, dir);
if (isControlUpdated) {
// Run validity check only in case a control was updated (i.e. view validators were
// removed) as removing view validators might cause validity to change.
ctrl.updateValueAndValidity({emitEvent: false});
}
}
}
private _updateRegistrations() {
this.form._registerOnCollectionChange(this._onCollectionChange);
if (this._oldForm) {
this._oldForm._registerOnCollectionChange(() => {});
}
this._oldForm?._registerOnCollectionChange(() => {});
}
private _updateValidators() {
@@ -179,9 +179,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
/** @docs-private */
ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeControl(this);
}
this.formDirective?.removeControl(this);
}
/**
@@ -286,7 +286,7 @@ export class NgSelectOption implements OnDestroy {
@Input('value')
set value(value: any) {
this._setElementValue(value);
if (this._select) this._select._writeValueAfterRender();
this._select?._writeValueAfterRender();
}
/** @internal */
@@ -296,9 +296,7 @@ export class NgSelectOption implements OnDestroy {
/** @docs-private */
ngOnDestroy(): void {
if (this._select) {
this._select._optionMap.delete(this.id);
this._select._writeValueAfterRender();
}
this._select?._optionMap.delete(this.id);
this._select?._writeValueAfterRender();
}
}
+3 -4
View File
@@ -120,10 +120,9 @@ export function cleanUpControl(
// case. We still check the presence of `valueAccessor` before invoking its methods to make sure
// that cleanup works correctly if app code or tests are setup to ignore the error thrown from
// `selectValueAccessor`. See https://github.com/angular/angular/issues/40521.
if (dir.valueAccessor) {
dir.valueAccessor.registerOnChange(noop);
dir.valueAccessor.registerOnTouched(noop);
}
dir?.valueAccessor?.registerOnChange(noop);
dir?.valueAccessor?.registerOnTouched(noop);
cleanUpValidators(control, dir);
+1 -3
View File
@@ -162,9 +162,7 @@ abstract class AbstractValidatorDirective implements Validator, OnChanges {
const input = this.normalizeInput(changes[this.inputName].currentValue);
this._enabled = this.enabled(input);
this._validator = this._enabled ? this.createValidator(input) : nullValidator;
if (this._onChange) {
this._onChange();
}
this._onChange?.();
}
}
+22 -23
View File
@@ -997,8 +997,8 @@ export abstract class AbstractControl<
this.touched = true;
const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
this._parent.markAsTouched({...opts, sourceControl});
if (!opts.onlySelf) {
this._parent?.markAsTouched({...opts, sourceControl});
}
if (changed && opts.emitEvent !== false) {
@@ -1087,8 +1087,8 @@ export abstract class AbstractControl<
control.markAsUntouched({onlySelf: true, emitEvent: opts.emitEvent, sourceControl});
});
if (this._parent && !opts.onlySelf) {
this._parent._updateTouched(opts, sourceControl);
if (!opts.onlySelf) {
this._parent?._updateTouched(opts, sourceControl);
}
if (changed && opts.emitEvent !== false) {
@@ -1131,8 +1131,8 @@ export abstract class AbstractControl<
this.pristine = false;
const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
this._parent.markAsDirty({...opts, sourceControl});
if (!opts.onlySelf) {
this._parent?.markAsDirty({...opts, sourceControl});
}
if (changed && opts.emitEvent !== false) {
@@ -1184,8 +1184,8 @@ export abstract class AbstractControl<
control.markAsPristine({onlySelf: true, emitEvent: opts.emitEvent});
});
if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts, sourceControl);
if (!opts.onlySelf) {
this._parent?._updatePristine(opts, sourceControl);
}
if (changed && opts.emitEvent !== false) {
@@ -1230,8 +1230,8 @@ export abstract class AbstractControl<
(this.statusChanges as EventEmitter<FormControlStatus>).emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.markAsPending({...opts, sourceControl});
if (!opts.onlySelf) {
this._parent?.markAsPending({...opts, sourceControl});
}
}
@@ -1321,12 +1321,12 @@ export abstract class AbstractControl<
opts: {onlySelf?: boolean; emitEvent?: boolean; skipPristineCheck?: boolean},
sourceControl: AbstractControl,
): void {
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(opts);
if (!opts.onlySelf) {
this._parent?.updateValueAndValidity(opts);
if (!opts.skipPristineCheck) {
this._parent._updatePristine({}, sourceControl);
this._parent?._updatePristine({}, sourceControl);
}
this._parent._updateTouched({}, sourceControl);
this._parent?._updateTouched({}, sourceControl);
}
}
@@ -1415,8 +1415,8 @@ export abstract class AbstractControl<
(this.statusChanges as EventEmitter<FormControlStatus>).emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity({...opts, sourceControl});
if (!opts.onlySelf) {
this._parent?.updateValueAndValidity({...opts, sourceControl});
}
}
@@ -1604,7 +1604,7 @@ export abstract class AbstractControl<
*/
getError(errorCode: string, path?: Array<string | number> | string): any {
const control = path ? this.get(path) : this;
return control && control.errors ? control.errors[errorCode] : null;
return control?.errors ? control.errors[errorCode] : null;
}
/**
@@ -1729,8 +1729,8 @@ export abstract class AbstractControl<
const changed = this.pristine !== newPristine;
this.pristine = newPristine;
if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts, changedControl);
if (!opts.onlySelf) {
this._parent?._updatePristine(opts, changedControl);
}
if (changed) {
@@ -1743,8 +1743,8 @@ export abstract class AbstractControl<
this.touched = this._anyControlsTouched();
this._events.next(new TouchedChangeEvent(this.touched, changedControl));
if (this._parent && !opts.onlySelf) {
this._parent._updateTouched(opts, changedControl);
if (!opts.onlySelf) {
this._parent?._updateTouched(opts, changedControl);
}
}
@@ -1768,8 +1768,7 @@ export abstract class AbstractControl<
* @internal
*/
private _parentMarkedDirty(onlySelf?: boolean): boolean {
const parentDirty = this._parent && this._parent.dirty;
return !onlySelf && !!parentDirty && !this._parent!._anyControlsDirty();
return !onlySelf && !!this._parent?.dirty && !this._parent!._anyControlsDirty();
}
/** @internal */