Intro
Previously we added visibility
to AbstractControl, but, we are missing one crucial part - treating hidden control as VALID
. After that we will add another missing functionality: defaultValue
and clear()
method.
As always, full code examples are on my github.
What I also forgotten before is that in order for our application to apply all this changes, we must import our file in app.component
file as such:
import "../../../core/forms/dc-forms";
Handling hidden control
As it turns out, this part is the easiest one by far. We simply need to if statement
in updateValueAndValidity
method:
AbstractControl.prototype.updateValueAndValidity = function (opts: { onlySelf?: boolean, emitEvent?: boolean } = {}): void {
...
if (this.visible === false) {
(this as {status: string}).status = 'VALID';
} else if (this.enabled) {
...
}
...
}
Note that we add the if (this.enabled)
as an else
to our if because there is no need to run any validators on a hidden control.
Default value
When we first create FormControl
we can pass options to contructor, either as a initial value or value and other parameters. So why isn't this value treated as default when we reset the control? This only makes sense in the FormControl
so we will not do it FormGroup
and FormArray
(but of course you can).
First, we declare another interface in our augmented module:
declare module "@angular/forms" {
...
interface FormControl extends AbstractControl {
readonly defaultValue: any;
}
}
And then, override the _applyValue
method, adding another line just below the visibility handling from previous article:
(this as {defaultValue}).defaultValue = formState.value;
And that's it for storing the defaultValue. Now we can enhance reset()
and clear()
methods.
clear() and reset()
For compatibility, clear and reset will be declared both in AbstractControl
and in FormControl
interfaces:
interface AbstractControl {
...
clear(value?: any, options?: Object);
// @ts-ignore
reset(value?: any, options?: Object);
}
interface FormControl extends AbstractControl {
readonly defaultValue: any;
clear(options?: Object);
// @ts-ignore
reset(options?: Object);
}
The reason for different signature is that, as mentioned before, defaultValue
is mainly useful in FormControl
. For compatibility's sake the AbstractControl.clear()
method will be declared as follows:
AbstractControl.prototype.clear = function (value?: any, options?: Object) {
this.reset(value, options);
}
Now, for the actual functionality, we implement the FormControl
methods:
FormControl.prototype._applyValue = function(value: any, options?: Object) {
this._applyFormState(value);
this.markAsPristine(options);
this.markAsUntouched(options);
this.setValue(this.value, options);
this._pendingChange = false;
};
FormControl.prototype.reset = function (options?: Object) {
this._applyValue(this.defaultValue, options);
};
FormControl.prototype.clear = function (options?: Object) {
this._applyValue(null, options);
};
The _applyValue()
method is basically what reset()
looked liked before, so in terms of Vanilla Angular:
- our new reset() method sets the
FormControl
value todefaultValue
- our clear() method sets the
FormControl
value to null
What else ?
Now we can enhance our visibility functionality by:
- calling
clear()
when we hide our control - calling
reset()
when we show our control
declare module "@angular/forms" {
interface FormGroup extends AbstractControl {
reset(value: any, options: {onlySelf?: boolean, emitEvent?: boolean}): void;
}
}
...
AbstractControl.prototype.hide = function () {
if (this.visible) {
(this as { visible: boolean }).visible = false;
this.visibilityChanges.emit(this.visible);
this.clear();
this.updateValueAndValidity();
}
};
AbstractControl.prototype.show = function () {
if (!this.visible) {
(this as { visible: boolean }).visible = true;
this.visibilityChanges.emit(this.visible);
this.reset();
this.updateValueAndValidity();
}
};
One other thing we can do is to override the FormGroup.reset()
method to account for different signature of reset()
and clear()
:
FormGroup.prototype.reset = function (value: any = {}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._forEachChild((control: AbstractControl, name: string) => {
if ('defaultValue' in control) {
(control as FormControl).reset(options);
} else {
control.reset(value[name], {onlySelf: true, emitEvent: options.emitEvent});
}
});
...
};
Summary
Doesn't this look more natural in terms of usability ? We clear control when hiding it, and restore default when we show it again. Also, the hidden control is treated as VALID
, hence not iterfering in the FormGroup as a whole.
In the next article, we will tackle the last shortcoming of Angular Forms: lack of differentiation between user and programmer input.
Hope you found this article usefull :)
Top comments (0)