Help Keep Angular Forms Type-Safe
Help Keep Angular Forms Type-Safe
NOTE: This article is not longer relevant with the following being merged into the forms module. https://github.com/angular/angular/discussions/44513
Click here to share this article on LinkedIn »
NOTE”
Angular Reactive Forms are awesome and very powerful but in my opinion they have one GLARING fault, and that is that they are not type-safe. Let’s look at an example to demonstrate. (We will be using FormBuilder for all examples.)
class MyComponent {
myForm: FormGroup = this.fb.group({
firstName: [''],
lastName: ['']
email: ['']
});constructor(private fb: FormBuilder) {}
onSubmit() {
console.log(this.myForm.value);
}
}
Above we can see a simple FormGroup model defined with 3 properties, but if we were to hover over the “value” property in our IDE we would see the type is “any”! In this small example it might not be a big deal, but once we get into larger, more dynamic forms it can cause some hard to track down bugs.
While my preferred solution would be for AbstractControl to be a generic and allow something like this: (there is actually an issue for this here so fingers crossed.)
myForm: FormGroup = this.fb.group<MyFormModel>({
firstName: [''],
lastName: ['']
email: ['']
});
We can work around this limitation and at least make our type-SAFEISH if not actually type-safe. Our solution should meet 3 criteria:
- When we CREATE a form we should KNOW the shape of the object the form expects.
- When we UPDATE a form we KNOW the properties that are available to be updated.
- When we get the value of our form we KNOW it’s shape.
So how do we get started? Let’s take it one step at a time.
First we know that we want to define a model so let’s go ahead and do just that.
interface MyFormModel {
firstName: string;
lastName: string;
email: string;
}
We want to tell our form that it has three properties that are string. Next we need a method that returns a form and that accepts our model as an argument.
private createForm(model: MyFormModel): FormGroup {
return this.fb.group(model);
}
Now when we call “createForm” the compiler will tell us whether or not we have all of the properties. Next is a method for updating. (Partial is built in to TypeScript and makes every property of its argument optional)
private updateForm(model: Partial<MyFormModel>): void {
this.myForm.patchValue(model)
}
And finally, a property for looking at the value of the form. Here we can use casting to tell the compiler that the value of “myForm” is MyFormModel
get formValue() {
return this.myForm.value as MyFormModel;
}
Put it all together and we get…
import { Component } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';interface MyFormModel {
firstName: string;
lastName: string;
email: string;
}@Component(...)
export class MyComponent {
myForm: FormGroup = this.createForm({
firstName: '',
lastName: '',
email: ''
});get formValue() {
return this.myForm.value as MyFormModel;
}constructor(private fb: FormBuilder) {}
onSubmit() {
console.log(this.myForm.value);
}private createForm(model: MyFormModel): FormGroup {
return this.fb.group(model);
}private updateForm(model: Partial<MyFormModel>): void {
this.myForm.patchValue(model)
}
}
And that’s all! Now we can be a bit more confident when updating our forms and it will also now be much safer when we have to CHANGE anything about that form.
Thanks for reading! If you have any other idea on how to be safer when working for Reactive Forms let me know I would love to see them.