Strictly typed Angular Reactive Forms
Tags: angular, typescript, webdev
Published: 2021-08-03 17:10:45

  > Hi! Please feel free to correct my grammar (English is not my native language) or mistakes in code. I appreciate any feedback.

   The angular reactive form is a great tool to work with. But there is one issue that makes the experience not so smoother, let's talk about types. We can't use IDE autocomplete when try to access the form's `controls` property. We should always remember the correct spelling of names that we give to our controls and their value types, it's very annoying.
 ![TS hint without types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vkz6lxwkre93ri8p77y6.png)
   What can we do? Once, I was searching for solutions and found a cool [article](https://indepth.dev/posts/1198/angular-forms-story-strong-types) by [Georgi Parlakov](https://twitter.com/@gparlakov), highly recommend you to read it. It inspired me to improve Georgi's approach.
```typescript
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';

/**
 * Form.controls autocomplete with value types.
 */
export type FormControls<T> = {
  [key in keyof T]: T[key] extends TForm<any> | FormArray // If control value has type of TForm (nested form) or FormArray
    ? T[key] // Use type that we define in our FormModel
    : Omit<AbstractControl, 'value'> & { value: T[key] } // Or use custom AbstractControl with typed value
};

export type TForm<T> = FormGroup & {
  controls: FormControls<T>;
};
```
   Firstly, we need to create a generic type, which extends angular **FormGroup** and rewrites `controls` property to a custom dynamic type, let's call it **TForm<T>**. Then, we need to make another generic type for our `controls` property (**FormControls<T>**), `[key in keyof T]` helps us get access to a key (name of the control) and a value (type of the control) of each property inside our generic type (**ProfileFormModel**). We left `key` as it is, but value type depends on which type we pass into generic, if it's a nested form (**Form Group** or **FormArray**) we use it as a type of the control, otherwise let's use **AbstractControl** but with a few changes. 
`Omit<AbstactControl, 'value'> & { value: T[key] }` this construction allows us to use **AbstractControl** where `value` property was removed and added again, but now with a type.
```typescript
...
type AddressFormModel = { // <-- Nested form structure
  street: number;
  city: string;
  state: string;
  zip: string;
};

type ProfileFormModel = { // <-- Main form structure
  firstName: string;
  lastName: string;
  address: TForm<AddressFormModel>; // Strongly typed nested form
  aliases: FormArray;
};

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm: TForm<ProfileFormModel> = this.fb.group({
    firstName: ['', Validators.required],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
    aliases: this.fb.array([this.fb.control('')])
  }) as TForm<ProfileFormModel>; // <-- Need to specify type.

  get aliases() {
    return this.profileForm.get('aliases') as FormArray;
  }
...
```
   Next time when we use our form controls, we'll see that TS knows our form's structure and will autocomplete along with a typed value.
 ![TS lint hint example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khnb2lsib8jat5lr7hyt.gif)

[Stackblitz code](https://stackblitz.com/edit/angular-abbtqm)
   
Thanks for reading my first article, I hope you found it useful. Also you can support me by buying a coffee :)
<a href="https://www.buymeacoffee.com/ddev8"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee ;)&emoji=☕&slug=ddev8&button_colour=FFDD00&font_colour=000000&font_family=Lato&outline_colour=000000&coffee_colour=ffffff"></a>