(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 | //my dream and challenge |
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 | //example of usage |
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 | interface IHeroFormModel { |
Intellisense on FormGroupTypeSafe<T>.value:
Intellisense on FormGroupTypeSafe<T>.getSafe and then patching the value:
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 *
Define an interface of your form model.
1
2
3
4
5
6
7//interface used with FormGroupTypeSafe<T>
interface IHeroFormModel {
name: string,
secretLairs: Array<Address>,
power: string,
sidekick: string
}Declare your new FormGroupTypeSafe form with the help of TypeScript’s generics.
1
2
3
4/* TypeSafe Reactive Forms Changes */
//old code
//heroForm: FormGroup;
heroForm: FormGroupTypeSafe<IHeroFormModel>;Inject FormBuilderTypeSafe.
1
2
3
4
5
6
7
8
9constructor(
/* TypeSafe Reactive Forms Changes */
//old code - private fb: FormBuilder,
private fb: FormBuilderTypeSafe,
private heroService: HeroService) {
this.createForm();
this.logNameChange();
}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// old code
// this.heroForm = this.fb.group({
// name: '',
// secretLairs: this.fb.array([]),
// power: '',
// sidekick: ''
// });
this.heroForm = this.fb.group<IHeroFormModel>({
name: new FormControl(''),
secretLairs: new FormControl([]),
power: new FormControl(''),
sidekick: new FormControl('')
});
//***** Nested type sample *****
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 | this.heroForm = this.fb.group<IHeroFormModel>({ |
5. Use the typesafe form.
1 | //old code |
* 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 :)