A case for extending Angular Forms - part 4
Published: 2020-11-09 12:36:12

# Intro
[Previously](https://dev.to/digitalcrafting/a-case-for-extending-angular-forms-part-3-1c3l) 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.](https://github.com/DigitalCrafting/dev-to-examples/blob/main/angular/projects/core/forms/dc-forms.ts)

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:
``` javascript
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:

``` javascript
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:
``` javascript
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:](https://dev.to/digitalcrafting/a-case-for-extending-angular-forms-part-3-1c3l)
``` javascript
(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:
``` javascript
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:
``` javascript
AbstractControl.prototype.clear = function (value?: any, options?: Object) {
    this.reset(value, options);
}
```

Now, for the actual functionality, we implement the `FormControl` methods:
``` javascript
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 to `defaultValue`
- 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

``` javascript
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()`:

``` javascript
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](https://dev.to/digitalcrafting/a-case-for-extending-angular-forms-part-5-393g), we will tackle the last shortcoming of Angular Forms: lack of differentiation between user and programmer input.

Hope you found this article usefull :)