The earlier versions of Angular Reactive Forms were not fully type-safe, meaning that the value emitted by the form was of type any, and the framework did not have a lot of information available about the types of each form control.

So it was easy to accidentally run into typical type-safety errors when writing Angular Forms, such as for example assigning a value of the wrong type to a form field.

But starting with Angular 14 and going forward, Angular has added full built-in type safety to Reactive Forms, meaning that you can get useful error messages and auto-completion when working with form values, when patching form values, and in many other situations.

Unlike what you might think, you don't have to define extra custom types, or add a ton of type annotations to your form declarations to benefit from this extra type safety.

You can benefit from all this extra type safety without hardly having to make any code changes to your program, as we will learn in this post.

Table Of Contents

In this post, we will cover the following topics:

  • The Best Way to Use Angular Typed Forms
  • Avoid This Common Typed Forms Pitfall
  • Why You Don't Have to Define Extra Types (or switch to the constructor API)
  • How To Declare Nullable Fields
  • The NonNullableFormBuilder service
  • Summary

This post is part of our ongoing series on Angular Forms, you can find all the articles available here.

So without further ado, let's get started learning everything that we need to know about Angular Typed Forms!

The Best Way to Use Angular Typed Forms

So what is the most convenient way of using typed forms? What I like to do is to simply declare my reactive forms using the FormBuilder API as usual, without adding any extra type annotations, or defining extra custom types.

Here is a typical example of the declaration of a form for a login page with two fields (email and password):

As you can see, I'm just declaring this in the normal way as we have always done.

And believe it or not, type safety is already in place, and Angular knows that this form has two fields called email and password, both of type string!

To confirm this, let's check if auto-completion is working in the login method:

Angular Typed Forms - Auto-completion example for the form value

As we can see, we get auto-completion and type safety already, without having to do anything special!

Let's see another example of type safety in action, when using the form
patchValue() API:

Angular Typed Forms - Auto-completion example for patchValue

Again we have auto-completion enabled automatically.

But most of all, we get useful compiler error messages, in case we are passing a value of the wrong type to a given form field:

Angular Typed Forms - Example of helpful compiler error messages

So how is this working? Everything is happening thanks to the Typescript compiler, and its powerful type inference mechanism.

The compiler is inferring the names of each form field and their types as well, based on the initial value of each form field.

Because both fields are initialized to the empty string, Angular is inferring that both fields are of type string.

To be more precise, and because all form fields are considered nullable by default, the email and password fields are inferred to have the string | null union type.

We will talk much more about nullable fields later in this post.

Right now, let's just realize how powerful Angular Typed Forms are: we get working type safety without having to add any extra type annotations, how cool is that?

But we only get this benefit if we use the FormBuilder as it was designed to be used.

Let's now see a couple of examples of when things could go wrong.

Avoid This Common Typed Forms Pitfall

While trying to leverage typed forms, a very common pitfall that you want to avoid is to declare your form member variables as having the explicit type
FormGroup, like so:

What could be more natural, right? We are declaring the form member variable, and initializing it on ngOnInit, which is a super-common and well-established pattern.

This way of declaring forms might seem natural, and you might have been used to declare forms in this way in your code.

But now, this approach is no longer advised as it prevents you from benefitting from full form type safety.

See for yourself, we now no longer get auto-completion when accessing the form value!

Angular Typed Forms - Auto-completion gone the form value, due to wrong declaration of the form member variable

And when using patchValue, we no longer get suggestions for form field names:

Angular Typed Forms - Auto-completion gone the form value, due to wrong declaration of the form member variable

Besides not having auto-completion, we will also not get helpful error messages from the compiler when assigning wrong values to a field, unlike before.

Why is this not working?

This way of declaring forms does not allow for our forms to be type safe, because we are declaring the form as of type FormGroup, which implicitly defaults to FormGroup<any>, meaning that the form value will be of type any.

As you know, any essentially means that the compiler does not have any type information about the form value, so all forms of type safety are turned off and the form is no longer type safe.

Notice that Typescript is still inferring all the type information from the form declaration, but then we are throwing that information away by assigning the form declaration to a plain FormGroup variable.

In order to avoid this issue and still have fully type-safe forms, simply use the method we first showed in this post, and let the type of the form variable be fully determined by type inference.

Why You Don't Have to Define Extra Types (or switch to the constructor API)

In order to make type-safe forms work, you also don't have to declare an explicit form type like this:

This extra type is not necessary. It's more readable and less verbose to simply rely on type inference instead.

You also don't have to switch to the constructor API to benefit from type safety.

For example, you don't have to write your form declarations in this way to benefit from type safety:

It's not that this is wrong or anything, but the FormBuilder API is just as type safe and less verbose, so I tend to prefer it.

How To Declare Nullable Fields

By default, all form fields are considered to be nullable. And this is because when we call the form reset() method, Angular will set all of the form fields to null.

This default behavior has been in there in Angular since the beginning, so that is why we need to consider every field as nullable, unless we configure it otherwise.

We can also optionally declare a form field as non-nullable, meaning that the form field will be reset to its initial value instead of null.

One of the ways of doing this is by using the constructor API:

In this example, we have declared the email field as non-nullable, while the password field remains nullable.

But this can also be done using the FormBuilder API, with the same results:

Let's now see what happens when we call reset in our form:

And here is the generated output in the browser's console:

Angular Typed Forms - example of reset of a non-nullable field

As we can see, the email field is now being reset to its initial value, instead of null.

The NonNullableFormBuilder service

If you are building a large form where all of its fields are non-nullable, then marking the fields as non-nullable one by one could become quite verbose.

A simpler way to do that is to switch to using the NonNullableFormBuilder service, instead of FormBuilder.

Using this service, we can simply declare our fields without mentioning their nullability, and every field will be considered nullable by default:

And here is what the output of the console looks like after calling reset on this new form:

Angular Typed Forms - example of reset of a non-nullable field

As you can see, now all of the fields are considered non-nullable, as they are being reset to their initial value as expected (and not to null).

Summary

I hope that this post helped in understanding how the Angular Typed Forms feature works, and how we can best use it.

This feature is really super convenient!

Without any extra ceremony in our code like extra type annotations, and without having to switch to the constructor API, we now have full type-safety and auto-completion in our form-related code.

Everything happens almost by magic, due to the power of Typescript type inference.

In summary, I highly recommend that you start leveraging Typed Forms in your code. There is no downside to it, and it will help you prevent countless bugs and make your form code that much more maintainable in the long run.

I hope that you have enjoyed this post, if you would like to learn a lot more about Angular Forms (both Template-Driven and Reactive), we recommend checking the Angular Forms In Depth course, where Typed Forms are covered in detail.

Also, if you have some questions or comments please let me know in the comment section below and I will get back to you.

To get notified of upcoming posts on Angular, I invite you to subscribe to our newsletter:

And if you are just getting started learning Angular, have a look at the Angular for Beginners Course: