A case for extending Angular Forms - part 3
Tags: angular, typescript
Published: 2020-10-28 17:21:24

# Intro
After deciding what approach to use in my [previous article](https://dev.to/digitalcrafting/a-case-for-extending-angular-forms-part-2-5268) it's finally time to code. In this article I'll go through necessary changes to Angular `AbstractControl`  in order to add _visibility_ functionality.

# The todos

There are a couple of things we need to do:
1. Add the `visible` state
2. Add the onChangeEventEmitter
3. Add methods to control the state
4. Enable adding visible as a constructor parameter
5. Remove the validity check when the control is invisible

In order to achieve all of these, we will be altering some of the internal methods of the Angular framework. The best way to do this is to copy the code form their [Github repo](https://github.com/angular/angular/blob/master/packages/forms/src/model.ts) and apply the changes.

The fifth point form our todo list is not going to be implemented in this article since it is **heavily** interfering with the internals of the framework and as such, deserves more attention.

# The implementation

In order to be able to use `prototype` we will be using [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation):

``` javascript
declare module "@angular/forms" {
    interface AbstractControl {
        visibilityChanges: EventEmitter<boolean>;
        readonly visible: boolean;
        show();
        hide();

        /**
         * This methods is marked as internal inside the AbstractControl.
         * Declaring it here allows us to easily override it
         * */
        _isBoxedValue(formState: any): boolean
    }
}
```

This is pretty straightforward. We 're-declare' the `@angular/forms` module and it's `AbstractControl` class (this **must** be declared as interface here, abstract class does not work) and then we declare new members of the class and private method we want to override.

Adding new functionality is also rather simple using the good old `prototype`:
``` javascript
(AbstractControl.prototype as { visible: boolean }).visible = true;
AbstractControl.prototype.visibilityChanges = new EventEmitter<boolean>();
AbstractControl.prototype.hide = function () {
    if (this.visible) {
        (this as { visible: boolean }).visible = false;
        this.visibilityChanges.emit(this.visible);
        this.reset();
        this.updateValueAndValidity();
    }
};
AbstractControl.prototype.show = function () {
    if (!this.visible) {
        (this as { visible: boolean }).visible = true;
        this.visibilityChanges.emit(this.visible);
    }
};
```

There are 2 things I want to point out:
1. Casting control as `{ visible: boolean}` - this one is from Angular framework itself, it allows us to modify the `readonly` value inside our class, while keeping it non-modifiable outside of it.
2. Additional calls in the `hide()` - when hiding the control, we have to remember to clear it's content and update validity. Right now we do not have the full functionality to do this correctly - it will come in the following articles - so these two methods have to suffice.

So that's points 1-3 done, now let's take a look at number 4.

Angular allows us to pass either value or value and disable state into the `FormControl` constructor. Wouldn't it be nice to also be able to pass initial visibility ? In order to achieve this, we need to override two more methods:
1. `AbstractControl.prototype._isBoxedValue` and 
2. `FormControl.prototype._applyFormState`

They check if the passed state is value or object, and assign the object values to form state respectively.

In the first one we just need to extend *if statement* a bit:
``` javascript
AbstractControl.prototype._isBoxedValue = function(formState: any): boolean {
    return typeof formState === 'object' && formState !== null &&
        Object.keys(formState).length >= 2 && 'value' in formState &&
        ('disabled' in formState || 'visible' in formState);
};
```

But the second one is a bit more tricky. We need to remember that we do not require passing both `disabled` and `visible` params so if the `visible` was `undefined` it would be interpreted as a `false` in the *if statement*, hence hiding our control when we didn't want to. That's why we will specifically extend the second method to only allow `true` or `false` values and nothing else:
``` javascript
(FormControl.prototype as { _applyFormState: () => void })._applyFormState = function(formState: any) {
    if (this._isBoxedValue(formState)) {
        // ...
        if (formState.visible === true || formState.visible === false) {
            (this as {visible: any}).visible = formState.visible;
        }
    } else {
        // ...
    }
};
``` 

Full code for extending the Angular Forms is available [here.](https://github.com/DigitalCrafting/dev-to-examples/blob/main/angular/projects/core/forms/dc-forms.ts)

# Summary

That's all the functionality for this article, as mentioned before, handling the validity of hidden control will be the topic of the next one. 

Full code along with the example is available in [my Github repo](https://github.com/DigitalCrafting/dev-to-examples).

Thank you for reading. Hope you enjoyed this article and will find it useful ! See you in the [next article.](https://dev.to/digitalcrafting/a-case-for-extending-angular-forms-part-4-1518)