When you don’t need template-driven or reactive forms

A common misconception shared by many Angular developers is that if you have a form, you need to use template-driven or reactive forms to handle it.

The truth is, in most cases, simple forms don’t need any of that. Instead, you can use template reference variables. My favorite example is a login form:

The two template reference variables used in the above example are sufficient to capture the username and password when the user clicks the log-in button. There is no need for extra complexity!

We lose some features along the way, such as reactivity to changes and automatic form validation. For instance, if I use this expression, the value will not change unless the component gets refreshed by another change:

This is because no Angular event listener triggers change detection when the form input value changes. But again, for my example of the log-in form, it doesn’t matter.

You can check out a live example here where I capture updates when the user clicks a button to emulate some reactivity. For anything more complex than such an example, using template-driven or reactive forms makes perfect sense.

If you want to learn more about similar tips and tricks through code challenges delivered to your inbox twice a week, check out the Angular Accelerator program. It’s currently open for 5 free scholarships, and there’s a 5-day free trial no matter what!

FormGroup: All you need to know

Recently, during an Angular Accelerator coaching call, my client asked what is the point of using FormGroup. I then realized I had never written about FormGroup, so here is a post to fix that.

When using reactive forms, we always have at least one FormGroup, which happens to be the entire form. This doesn’t mean that Form Groups are only designed for that purpose: A complex form can be divided into multiple groups. For instance, if we have a form where a user is supposed to enter their name, address, birth date, etc., we could group some of these controls into FormGroups.

What do we get out of that? A few different things:

  1. Form groups aggregate the validation state of all individual controls into a group state. If an address is made of 4 controls (street, city, zip code, country), then the form group for these 4 controls will be invalid when any controls are invalid, touched when any control is touched, etc.
  2. Form groups aggregate actions on all individual controls within the group. Want to disable address entry? Instead of disabling all 4 controls one by one, you can do addressGroup.disable() , which is a lot less error-prone (you can’t forget one of these controls)
  3. Form groups store the value of all controls as a single object. In my example with 4 controls for an address, accessing addressGroup.getValue() would return:

So, the main benefit is to have grouped actions and status updates for a bunch of form controls. Let’s take another example with actual code (you can find the full code base on Stackblitz here):

If I use the following expression in my template to see the value of my userInfo, here’s what I get:

As you can see, my form group creates an object that updates when individual control values change. Perfect! Now, let’s click a button that sets the form group value to an object:

Now, I can use multiple values with one line of code. There is no need to call setValue() on all controls one by one. That’s cool. What if I want to apply a “diff” to my group and not set the entire thing? Say I wanted to be younger but not change my name or anything else:

Now, I can “patch” my form group with some localized changes, and I don’t need to worry about the other values of the controls of that group. That’s the magic of formGroup.patchValue().

You can also reset the entire group with formGroup.reset(), add or remove controls and validators, and more. The full API is here. That’s the power of FormGroups in action!

Tutorial: Architecting forms with Signals

In today’s post, I want to showcase several Angular framework features in one example. More specifically, we’ll use standalone components, signals with model() and input(), as well as forms.

Our goal is to build a reusable date-range picker component to select two dates:

To do so, let’s create a standalone component with two HTML date inputs (no need for component libraries!):

We use ngModel (which is from FormsModule) to capture the current value selected by the user and set a default value for those dates if needed. To do so, let’s use two signals created by the model() function from @angular/core:

Two important things to note:

  1. These two variables are signals and work seamlessly with ngModel
  2. These variables enable two-way data bindings with the parent component as follows:

start and end are signals as well. We can use those signals to pass a default value to the child component or keep that value empty if we don’t want any:

And that code is 100% functional as-is. No need for template-driven forms or reactive forms! Now, let’s add some features to our date-range picker component. Let’s say we want to enforce the rule that the start date cannot be after the end date and vice versa. The following bindings do the trick using the min and max HTML attributes:

Because this is all based on signals, it’s all reactive by default. Changing the start date or the end date automatically updates the range of what’s selectable in the other date input:

Adding more features is now as easy as adding more signals and bindings. For instance, I want to enforce a minimum start date. I added a new signal input called minDate and I bind it to the min attribute of our startDate:

And then, in the parent component, we can pass a minimum start date:

You can find that code example in action on Stackblitz here. Note that our date range picker has less than 20 lines of code and relies on basic HTML features (input, min, max) instead of adding dependencies that would impact performance.

Please email me if you’d like me to add more features to that example in a future post. I’m always happy to cover what really matters to most people reading these posts.

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:

Dynamic forms with FormArray

We’ve covered reactive and template-driven forms in the past. One of the nice features of reactive forms is the FormArray class. You can use FormArray for a form where the number of inputs is dynamic and can increase or decrease:

To achieve the above, we create a FormArray in our component and add a method so we can append new FormControls to that array:

Then, we use a @for block to display all controls of that array in our template, including a button to add new names to the list. Note that we store the index of each control in a local variable i:

We can even add support for the removal of form elements using the removeAt method of FormArray and the index of the element to remove:

And that’s it! We can check that the FormArray value is updated correctly with the following expression:

You can see that code in action on Stackblitz here. You can read this excellent guest post from Jennifer Wadella on my blog for more information on dynamic forms.

Using FormBuilder or FormControl?

When creating reactive forms with Angular, we have two options available. The most documented one is to inject the FormBuilder service and use it to generate your form’s structure:

FormBuilder makes perfect sense for forms that contain lots of elements. This service gives us methods that are factories for the following syntax, which is a lot more verbose:

Now, if you have a simple form with just one or two form controls, you don’t need FormBuilder. Declaring your FormControl “manually” is actually more manageable that way:

You can find an example of a single FormControl with custom validation here on Stackblitz.

ngx-mask for user input masking

HTML forms are evolving but still lacking a little in masking, which really means “suggesting the proper format to the user.

For instance, if I need to capture a US phone number in a specific format, I’d like to use a mask that looks like this:

We could use a placeholder, but the placeholder disappears as soon as we start typing, whereas a mask should remain visible and try to make the user input fit into that format. For instance:

This is where ngx-mask helps. It’s a third-party library that passes my acceptance criteria for dependencies. ngx-mask comes with directives and pipes to implement customizable masks.

To install it: npm install ngx-mask

You can find out how to configure the library here. Here is an example of the mask directive used for a US phone number:

And another one for a French phone number that doesn’t show the mask as we type but still enforces it:

The result looks like this:

Here is a link to my example on Stackblitz.

You can try many different demos on this website. The library supports regular expressions and lots of other options, such as a validation attribute to invalidate the form when the input is invalid:

And it’s possible to have multiple different acceptable formats on a mask, too:

Asynchronous form validation with Angular

We’ve already covered how to write custom form data validators with Angular. We also covered how to display custom validation feedback to the user. Today, let’s take a look at how to perform asynchronous validation of our form data, which is useful when we need to make an HTTP request to a server to validate user input.

The approach for asynchronous validation is very similar to what we do with regular validation. The only difference is that instead of returning ValidationErrors, we return an Observable or a Promise of such ValidationErrors:

A ValidationErrors object is any keys and values you want to return. I like using keys that are the name of the form input throwing the error and the value being the error message I want to display on the screen. For instance, this is one of my typical ValidationErrors objects:

If you want to dig deeper, here is a link to a tutorial showcasing an example of an async validator. It’s also worth noting that asynchronous validators introduce a new validation state besides VALID and INVALID, which is PENDING. You can use that validation state to display a spinner or temporarily disable a form input while the asynchronous validation occurs.

How to create custom form controls with Angular?

When working with lots of forms, you’ll likely use similar controls repeatedly: A country selection dropdown, a state selection dropdown, a date-range picker with specific constraints, etc.

Instead of duplicating our code, which is never a good idea, Angular allows us to create custom form controls that integrate with Angular’s form validation mechanism.

This feature requires our custom control component to implement an interface called ControlValueAccessor that has the following methods:

I have a detailed tutorial and code example if you want to dive deeper into that topic.

It’s also worth noting that you don’t have to start from scratch when implementing a control value accessor. Angular has a DefaultValueAccessor directive (the one used by FormControl, NgModel, etc., in Angular forms) that can be invoked with the selector ngDefaultControl, and there are a few more options available, such as the SelectControlValueAccessor.

Prefer template-driven forms

International readers might not know that today is a public holiday in the United States as the country celebrates its birth and Independence. As a result, I’ll make today’s post very short but still useful.

Over the past couple of newsletters, I explained that template-driven forms are perfectly fine in most cases. A few years ago at ng-conf 2021, Ward Bell said he always uses template-driven forms and explained how he does that in less than 19 minutes.

If you’d rather read a short article, you can head to my “reactive template-driven forms” post where I showcase how template-driven forms are actually… a layer on top of reactive forms, and that you could still access reactive features on a template-driven form if you wanted to. I know, it’s mind-blowing!

Happy 4th of July if you’re in the US!