Angular Typesafe Reactive Forms With TypeScript

Ruan Beukes ·

4 min read Original article ↗

(Update - March 2020 I finally created a npm package angular-typesafe-reactive-forms-helper)

Problem

Working with more complex Reactive Forms, it was challenging to remember how the form was bound to my FormGroup object.

I wanted intellisense to help me.

This is because the FormGroup class has a ‘value’ property which returns ‘any’, the cause of my frustrations.

Good luck renaming property names!

1
2
3
4
5

get value: CustomType;


get value: any;

Another frustration I faced was referring to the form properties names as ‘string’ values. It quickly got out of hand when I renamed property names.

1
2
3
4
5
6
7
8
9

this.form.get('heroName').patchValue('He-Man');


this.form.get(x => x.heroName).patchValue('He-Man');


get(path: Array<string|number>|string): AbstractControl|null

Angular team is considering implementing Typesafe Reactive Forms

Here are the issues worth tracking:
* Proposal - ReactiveForms: add AbstractControl.getChild<T> method
* Reactive forms are not strongly typed

Can’t wait for that to be fully implemented…until then, I’ll hack my way to typesafety :)

Solution

Quick Demo

The Interface used for all the code samples

1
2
3
4
5
6
interface IHeroFormModel {
name: string,
secretLairs: Array<Address>,
power: string,
sidekick: string
}

Intellisense on FormGroupTypeSafe<T>.value:

FormGroupTypeSafe.value Intellisense

Intellisense on FormGroupTypeSafe<T>.getSafe and then patching the value:

FormGroupTypeSafe.getSafe Intellisense

By having a lambda like expression instead of a ‘string’ property name, makes refactoring so much easier.

Typesafe Angular Reactive Forms Sample

I modified the Angular Reactive Forms Sample with my Typesafe Angular Reactive Forms Sample.

Search for TypeSafe Reactive Forms Changes to find all the changes. I commented out the original angular code and replaced it with the Typesafe way as a quick reference.

Files to focus on:

  • app/app.modules.ts
  • app/hero-detial.component.ts
  • app/angular-reactive-forms-helper.ts

*** app/app.modules.ts ***

Two things of importance here:

  • import the FormBuilderTypeSafe class
  • add to provider list for the Dependency Injection

See Typesafe Angular Reactive Forms Sample for details.

*** app/hero-detial.component.ts ***

  1. Define an interface of your form model.
1
2
3
4
5
6
7

interface IHeroFormModel {
name: string,
secretLairs: Array<Address>,
power: string,
sidekick: string
}
  1. Declare your new FormGroupTypeSafe form with the help of TypeScript’s generics.
1
2
3
4



heroForm: FormGroupTypeSafe<IHeroFormModel>;
  1. Inject FormBuilderTypeSafe.
1
2
3
4
5
6
7
8
9
constructor(


private fb: FormBuilderTypeSafe,
private heroService: HeroService) {

this.createForm();
this.logNameChange();
}
  1. Create your form group with Interfaces (contracts).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34









this.heroForm = this.fb.group<IHeroFormModel>({
name: new FormControl(''),
secretLairs: new FormControl([]),
power: new FormControl(''),
sidekick: new FormControl('')
});


interface IAddressModel {
suburb: string;
postcode: string;
}

interface ICustomerModel {
name: string;
address: IAddressModel
}

this.form = this.fb.group<ICustomerModel>({
name: new FormControl(null, [Validators.required]),
address: this.formBuilder.group<IAddressModel>({
suburb: new FormControl(''),
postcode: new FormControl('', [Validators.required]),
})
});

Benefits TypeScript provide:

  • Detecting errors (contract mismatch): Let’s say we change the ‘name’ property, ‘name’ => ‘nameNew’, but the IHeroFormModel.name did not change, TypeScript will raise the alarm.

  • Renaming properties: Super easy, I’m using VSCode and it’s as easy as F2, rename the property, enter…and TypeScript will do the rest. No more search and replace.

  • No more misspelled properties: Intellisense ensures you get the correct property name everytime. With string property names a mistake can easily creep in.

  • Guidance on how to create the form group: Code sample below will have TypeScript raise the alarm when attempting to set ‘name’ to ‘’, FormGroupControlsOf<T> returns FormControl | FormGroup.

    message: ‘Argument of type ‘{ name: string; secretLairs: FormControl; power: FormControl; sidekick: FormControl; }’ is not assignable to parameter of type ‘FormGroupControlsOf‘.

Types of property ‘name’ are incompatible.
Type ‘string’ is not assignable to type ‘FormControl | FormGroup’.’
at: ‘1, 49’
source: ‘ts’

1
2
3
4
5
6
this.heroForm = this.fb.group<IHeroFormModel>({
name: '',
secretLairs: new FormControl([]),
power: new FormControl(''),
sidekick: new FormControl('')
});

5. Use the typesafe form.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


this.heroForm.getSafe(x => x.name).patchValue('new Hero');



this.heroForm.setControlSafe(x => x.secretLairs, addressFormArray);

formatAddressToOneLine(addressIndex: number) : string {










let address = this.heroForm.value.secretLairs[addressIndex];
return address.street + ", " + address.city + ", " + address.state;
}

*** app/angular-reactive-forms-helper.ts ***

In order to make this work as closely as possible to the angular way I created an abstract class FormGroupTypeSafe<T> which derives from angular’s FormGroup.
It also ensures that exisitng code will not break.
I also created two functions: getSafe, setControlSafe.
Those were the only functions I needed for the Typesafe Angular Reactive Forms Sample.
If you need more, add them :)

You can find the implementation of angular-reactive-forms-helper.ts on my Github account or have a look at the Plunker example: Typesafe Angular Reactive Forms Sample.

Conclusion

I don’t misspell property names anymore and refactoring Reactive Forms is back to being a trivial task.
I think the extra bit of ceremony is absolutely worth it.

Use it…don’t use it :)