Angular Reactive Forms typed!
Tags: angular, reactive, forms, typed
Published: 2019-08-26 12:35:51

---
title: Angular Reactive Forms typed!
published: true
description: Easily extend Angular Reactive Forms to make them typed.
tags: #Angular #Reactive #Forms #Typed
---

Angular Reactive Forms are great, they come with two way data-binding between the model and view, support for sync/async validation, event handling, and status; but they don't come with typing support.

Lucky for us, adding this feature to Reactive Forms is very easy, and we'll see how we can do that in this tutorial.

First, we'll create our own generic versions of `FormGroup<T>` and `AbstractControl<T>` class. We'll just override the minimum required methods and properties to make our forms typed. Let's create a file called `forms.ts` and the following code.

```TS
import {
  FormGroup as NgFormGroup,
  AbstractControl as NgAbstractControl,
  ValidatorFn,
  AbstractControlOptions,
  AsyncValidatorFn
} from '@angular/forms';
import { Observable } from 'rxjs';

export abstract class AbstractControl<T = any> extends NgAbstractControl {

  readonly value: T;
  readonly valueChanges: Observable<T>;
  
  abstract setValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void

  abstract patchValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void
}

export class FormGroup<T = any> extends NgFormGroup {
  readonly value: T;
  readonly valueChanges: Observable<T>;

  constructor(controls: { [key in keyof T]?: AbstractControl; },
    validatorOrOpts?: ValidatorFn | Array<ValidatorFn> | AbstractControlOptions | null,
    asyncValidator?: AsyncValidatorFn | Array<AsyncValidatorFn> | null) {
    super(controls, validatorOrOpts, asyncValidator);
  }

  patchValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void {
    super.patchValue(value, options);
  }

  get(path: Array<Extract<keyof T, string>> | Extract<keyof T, string> | string): AbstractControl | never {
    return super.get(path);
  }

  controls: {
    [key in keyof T]: AbstractControl<T[key]>;
  };
}
```

Next, we'll create the form model, in this case a simple sign-up. Let's create a file called `sign-up-form.ts`.

```TS
export type SignUpForm = {
  email: string,
  password: string,
  optInNewsletter: boolean
}
```

Finally, we'll implement the generic `FormGroup` in our component. We instantiate the generic `FormGroup` with the `SignUpForm` type. Notice we import our `FormGroup` and not Angular's.

```TS
//...
import { FormGroup } from './forms';

export class AppComponent implements OnInit {

  form = new FormGroup<SignUpForm>({
    email: new FormControl(''),
    password: new FormControl(''),
    optInNewsletter: new FormControl('')
  })

  ngOnInit() {
    this.form.patchValue({
      email: 'joaqcid@gmail.com',
      optInNewsletter: true
    })
  }

  submit() {
    console.log("typed forms", this.form.value)
  }
}
```

Here we can see, how Typescript picks the Type used, and type-checks when we create the FormGroup, or we `patchValue` and returns the Type defined when we get the `value`

![Type-check form value](https://thepracticaldev.s3.amazonaws.com/i/v6gdlsa8v7yf5uqfnubi.png)
*Type-check form value*

![Type-check form model when instantiating FormGroup](https://thepracticaldev.s3.amazonaws.com/i/h7n2xgis8ni6fjheil27.png)
*Type-check form model when instantiating FormGroup*

![Type-check form model when patching value](https://thepracticaldev.s3.amazonaws.com/i/nx8247kzs9jbfnanlfps.png)
*Type-check form model when patching value*

![Type-check field value when patching value](https://thepracticaldev.s3.amazonaws.com/i/ilycydxrncp411pradyw.png)
*Type-check field value when patching value*

Cool! Isn't this great? Forms on steroids! 💉💊💉💊

Finally let's extend the `FormControl<T>` class in case we want to need to work with them as well.

On `forms.ts` let's add the followring code.

```TS
import {
  FormControl as NgFormControl,
  //...
} from '@angular/forms';

//...
export class FormControl<T = any> extends NgFormControl {
  readonly value: T;
  readonly valueChanges: Observable<T>;

  setValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void {
    super.setValue(value, options);
  }

  patchValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void {
    super.patchValue(value, options);
  }
}
```

On our component lets import our own FormControl implementation and build one. Notice I'm using a custom type `NgbDate` from ng-bootstrap.

```TS
import { ..., FormControl } from './forms';
import { NgbDate } from '@ng-bootstrap/ng-bootstrap';

export class AppComponent implements OnInit {
    //...
    formControl =  new FormControl<NgbDate>('')

    ngOnInit() {
    //...
    this.formControl.patchValue(new NgbDate(2019, 9, 11))
    }
}
```

Again, by using the typed version of our FormControl, we can type-check the FormControl when we patch, set or get the value.

![FormControl typed](https://thepracticaldev.s3.amazonaws.com/i/9gt9lknh1s8c2jlft807.png)
*FormControl typed*

## Conclusion

With these few lines of code we were able to extend Angular's Reactive Forms to make them typed, this can help us to type our form and add other custom functionality if we need too. Be careful when modifying behaviour as it may impact the current Form behaivour.

You can check the full example in this [StackBlitz](https://stackblitz.com/edit/angular-reactive-forms-typed).

This post have been written after reading different approaches in an open [Angular's github issue](https://github.com/angular/angular/issues/13721), there are also libraries such as ngx-typed-forms, that also provide similar functionality.

---

Hope you enjoyed this post, and if you did, please show some ❤️. I often write about Angular, Firebase and Google Cloud platform services. If you’re interested on more content, you can follow me on [dev.to](https://dev.to/joaqcid), [medium](https://medium.com/@joaqcid) and [twitter](https://twitter.com/joaqcid).