Angular Reactive Forms: Tips and Tricks

Netanel Basal
Netanel Basal
Published in
6 min readFeb 26, 2019

--

With every article I publish, my goal is to help you become a better and more confident coder.

Here are a couple of pointers to help shed some light on Angular Forms:

Control Validation

By default, whenever a value of a FormControl changes, Angular runs the control validation process.

For example, if you have an input that is bound to a form control, Angular performs the control validation process for every keystroke.

Let’s see this in action:

Now, imagine a form with complex validation requirements — updating such a form on every keystroke could become too costly. In addition to that, I find it very annoying to display an error message to the user when he hasn’t completed the action of entering the form data.

The way we get around this is by using the updateOn property. We can tell Angular that we only want to run the validation function upon submit or blur. As we saw, the default option is upon change.

updateOn: ‘blur’

We can also apply this option to a formGroup or a formArray.

Ok, that’s great. But let’s say we still want to run our validators whenever form control’s data changes, how can we optimize them?

In such a case we don’t have to use the control validator mechanism, we can create an alternative which we can fully control ourselves.

Luckily, every control exposes a valueChanges observable that we can take advantage of, and use RxJS to do some more powerful stuff. For example, we can add a debounce to our control.

And this is only one simple example. We can use more powerful operators to perform advanced cross-control validations like merge, combineLatest, etc., so keep that in mind.

I also wanted to mention that I’m against premature optimization— you should always prefer readability and reusability, and only apply optimizations when you think you may need them.

There might be cases when we don’t want to invalidate the form just because a single control is invalid. I haven’t come across such a case, but maybe you have.

In such a case the message you’re trying to convey to the user is: “this value isn’t valid, but it doesn’t mean the form can’t be submitted”.

In this instance we can set the onlySelf property to true, which means that each change only affects the control itself alone, and not its parents.

Preventing Infinite Loops

In the process of setting up our form controls, we might inadvertently cause infinite loops.

For example, if on the one hand we listen to the store’s changes and update the form accordingly, and on the other hand we listen to the form’s value changes and update the store.

In order to avoid the above situation, we can tell Angular not to emit the valueChanges event whenever we update the form.

And now we’re good to go. It is worth noting that most of a control’s methods support this option, such as setValue(), reset(), updateValueAndValidity(), etc.

Control Disabling

As you may know, when we need to disable or enable a control we can call the control.disable() or control.enable() methods.

That’s helpful, but sometimes we want to tell Angular that this control is disabled on the initialization face. We can achieve this by passing an object to our FormControl constructor.

Pay attention that we must also pass the value property; otherwise Angular assumes that we want the control’s value to be { disabled: true }.

My next tip is something I think many people haven’t noticed yet. Whenever we call a control’s disable() or enable() methods, Angular triggers the valueChanges event.

Yes, it may surprise you because the “value” didn’t change, but this is how it works.

There are times when we want to prevent this behavior from occurring.

Let’s say we need to update the store upon form’s value changes. This behavior would cause redundant updates, since we’d also update the store whenever the control disability status changed.

Luckily as we mentioned before there is an easy fix, we can add { emitEvent false } to the current method:

When we want to get our FormGroup value we usually call the value property. For example:

But there’s one catch there. The value property will omit controls that are disabled. So, in the above example, we’ll only get the email control value.

In most cases, this is not the desired behavior. In order to get the complete form’s values, we can use the FormGroup.getRawValue() method.

Control Getters and Setters

I want to talk about a common mistake that I see in people’s code examples, especially on Stackoverflow. When they need to add a new control, they assign it directly to the parent’s controls property:

Please don’t do this. Instead, use the addControl() method, since it performs multiple required tasks for us under the hood:

Angular source code

Another thing I want to talk about is obtaining a reference to a form control. This is more of a personal stylistic preference, but I don’t like to get it from the group’s controls property:

Angular provides the group with a get() method, and I find it to be a more preferable option.

Another reason to prefer this — the Angular team might opt to change the FormGroup structure in the future, and if we use the controls property directly, it might lead to a breaking change, whereas the get() method could adjust to reflect the new structure.

This might be why Angular provides us with the get() method, but hey, maybe it’s just me.

Using FormBuilder

There are times when we need to create a large form structure. In this case, the code can rapidly become repetitive and full of boilerplate. For example:

In reality, it might be even larger than that, but you get the point. In order to make our life easier, Angular provides us with the FormBuilder service, which frees us from adding boilerplate. I think it’s a better option, as it enables much cleaner code.

That’s it 😀

Additional Resources

🚀 In Case You Missed It

  • Akita: One of the leading state management libraries, used in countless production environments. Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which all help to manage the data and negate the need for massive amounts of boilerplate code.
  • Spectator: A library that runs as an additional layer on top of the Angular testing framework, that saves you from writing a ton of boilerplate. V4 just came out!
  • And of course, Transloco: The Internationalization library Angular 😀

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.