5 tips for Strictly Typed Reactive Forms

Forms can be a very complex part of any web application, and today, I will cover some useful tips and tricks for using Reactive Forms with strict types.

  1. Use nonNullable: true to restore default values on reset

Form controls and groups are nullable by default in Angular, which means that the following example of FormControl value isn’t of type string:

Instead, if you try to read email.value, you’ll see that the type is string or null. Why would that be the case since we have a default value? That’s because the form can be reset with a <button type="reset"> on the form or by calling email.reset(), for instance.

The trick then is to make that control non nullable by adding the following option:

Now, when the form gets reset, email.value is equal to "test2@gmail.com", which is perfect in several different form scenarios, such as an edit form, where we don’t want to “lose” the previous values, but just “reset” to those values.

2. Using non-nullable form controls at scale

If we want to apply that same config to several form controls, we can put them in a FormGroup and pass that same nonNullable: true option to the FormGroup constructor instead of specifying it for every single control. But what if we use the FormBuilder service?

Then we can use the following syntax:

3. How to define the type of a FormControl with no default value?

If we do something like this:

We’re in trouble because Typescript will infer the type of password.value is null. Instead, we use generics and a union type to specify the expected type for that value, here string or null:

4. How to read the values of disabled form controls?

In complex forms, it is very common that some controls are enabled/disabled based on some condition. For instance, if the user selects the country “USA, ” we might enable a state dropdown with the US states in it and disable it for other countries.

When a control is disabled, its value doesn’t appear as part of the form group value. Here is an example where age is obviously equal to 21, but since that control is disabled, formGroup.value doesn’t return it:

The workaround here is to use formGroup.getRawValue(), which will return everything, including disabled control values:

You can see that example in action on Stackblitz here.

5. How to handle dynamic forms where we don’t know which controls will be present ahead of time?

In some cases, forms can be completely different depending on the type of user, country, etc. As a result, we don’t know whether a control will be present or not at runtime. We can define that uncertainty in Typescript using the optional operator ? when we define the type of our form:

In the above code, password is defined as optional in our interface, meaning that the compiler will not throw an error if we remove that control later.

If your form is 100% dynamic and handling proper types for each scenario would be a nightmare, you can use the more flexible FormRecord instead of FormGroup:

Alain Chautard

Alain is a Google Developer Expert in Web Technologies, Angular, and Google Maps. His daily mission is to help development teams adopt Angular and build at scale with the framework. He has taught Angular on all six continents! A world traveler and photographer, Alain is also an international conference speaker, and a published author of several video courses.