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.
- 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
: