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
.
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:
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 😀
No comments:
Post a Comment