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
FormControlvalue todefaultValue - our clear() method sets the
FormControlvalue 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)