Angular Forms: Useful Tips

Tram Ho

This article gives you some helpful tips when working with Angular Forms.

Angular Forms are an important part of almost all Angular apps, even more so for dashboards, live data apps, data collection, etc.It is very important to be as flexible as possible, because The requirements for forms can vary a lot in a moment.

There are some important things to know and remember about Angular forms.

Forms are not type-safe

Forms are not a safe type, so you cannot rely on TypeScript to catch errors, code is susceptible to errors and spelling errors, which means you have to be more careful when handling forms. But of course just being cautious isn’t the only useful thing – there are some things we can do to make our lives easier.

  1. Limit the use of Formgroup.get when referring to nested controls in Formgroup. Instead, store references to nested controls in component propertie.
  2. Use the DTO pattern when changing Forms data data to a pattern required by a data service.
  3. Abstract away harder form controls to custom components implementing ControlValueAccessor.

This part is really confusing for newcomers to Angular so I will clarify each of the above with specific examples.

1. Limit the use of Formgroup.get

Consider this code:

This is a simple Formgroup component. Now take a look at the template:

Looks like a fairly regular form setting, nothing too hard. But did you see the problem?

Let’s take a look

In line

I mistakenly wrote ‘larstName, instead of’ lastName, and it may have caused an error and I could easily detect it in the console log,

Error is also normal when I have to code not only the error so you open the console log to see right away, but there is another thing, that is: you have enough patience to sit in the form.get('firstName') of course form.get many IDEs or text-editor’s plugins will still prompt the command, but firstName for example, won’t, you have to type it yourself. And that is also the cause of bugs just because you misspelled. So is there any other way? Take a look below

This is pretty simple: I just stored my references to my form controls in a special object called controls so I could use them easily in my form, which will now look like this:

At first glance, this may seem like a small improvement, but let’s see what it can offer

  1. FormControl.get is a function and calls it inside a template which means it will be called on every change detection cycle (a lot), but by using this method, I also solved that problem.
  2. I only store references for controls that will be referenced in the template
  3. If we need the controls within the component class’ code, we can also access them through these references and avoid repeating.

So one of the above problems has been solved, but what about type-safety? The value of my controls is still handled by TypeScript because they are of any type, which is not a good thing. So what should we do about it?

2. Use Data Transfer Object pattern

Imagine a situation where we have to fill in a complicated form and send it to the server via HTTP request. Of course, we can get the Formgroup.value and just send it, but chances are the server API gateway is waiting for a slightly different data model. For example, it can receive Date in ISO format instead of the normal JS string. Of course, we can change the shape of the Formgroup, but on the other hand, the controls that we use in the template may only work better with data types other than the server types expected. So what can we do?

DTO (data transfer object is a special object, responsible for bringing information from a subsystem (Angular app) to another system (backend). Its main and original purpose is to reduce the amount of data sent But it also ensures that subsystems talk to each other in the same language (the same type of data) So how will we benefit from this model?

By writing a simple class, it will take the form value in its constructor and create an object that exactly matches the server’s API signature, handling all differences between the value in my form and the server. . It is very important that their class does not have any other behaviors, getters, setters – that is, things that will not be serialized with JSON.stringify, and our class will have a sole responsibility – to create a data transfer. object. Here is an example of DTO:

This class is quite simple: it handles the conversion of an object (raw value) into another object (will move to the backend). Logic is completely encapsulated within the constructor. If there is a problem, the constructor is the only place where codebase can occur; If there is a problem with the type, the RawFormValue interface is where we should look. Here is an example of how we can use this template:

Writing like this is beneficial at a number of points:

  1. The business logic of the application with manipulation and data processing is removed from the component
  2. That logic is contained in a single place in my application, and it is solely responsible for that logic (easier to debug).
  3. This is great for any data manipulation you use, from simple old JS objects to state management and, for example, normalizing NGRX.

Custom Angular Controls

The last point I mentioned about making the form simpler is to create custom controls. Implementing ControlValueAccessor interface in a component and register it with the NG_VALUE_ACCESSOR provider that allows me to create a custom Angular Form Control. This means that my <custom-component></custom-component> can now get FormControl and ngModel like this:

This helps us abstract the complex behavior on forms and make it reusable. Whenever you find yourself doing a lot of custom heavy logic on a single control in Formgroup, think about whether you can turn it into another component.

setValue vs patchValue

There are two ways to change the value of FormControl in Angular: setValue and patchValue . They are essentially the same, but there is one important difference: if we call setValue on the Formgroup with an object missing some keys from the form signature, it will get an error. This is very useful, because it allows some kind of security in the dynamic world of Reactive Form. But there is a problem: it also gives an error whenever the object we pass to it has an attribute our form doesn’t have.

This will result in an error “Error: Cannot find form control with name: occupation”. This is quite useful, but comes with a serious drawback we must be concerned about. Consider this code:

In this case, I get some data from the server and set it as the form value – nothing particularly strange. So what is the problem?

Currently, everything works well, the server sends exactly the same data needed. But imagine this – the same API call we use to retrieve this user data is also used elsewhere in the application, in a component I didn’t build. And right there, the UI needs a bit more data about the user – so the API dev has added an occupation field, and thought, “when we added something, why would anything stop working?”. dynamic, because there is a new attribute on the object we try to setValue . So how do we solve this? There are two approaches:

  1. Use patchValue when setting values ​​from external API-s. This solves the immediate problem, but may create other problems in the future – what happens if the API design has a real change and some fields are missing in the response? Instead of seeing a blank screen on each input fields I have to see an error message and patchValue doesn’t throw.
  2. Complete solution: write an intermediate functio (or maybe a class) to convert the response server into something compatible with my signature form (like what we did when sending the form value to the server) ). For example:

We can see: If the API design changes, the only thing we will have to do is change this function.

Forms and events

An important feature of ReactiveForms is that we emit and read various events – control has been touched, control became dirty, value has changed, validity has changed, etc. I use that in many forms, but the most popular is a subscriber to FormControl.valueChanges Observable . This observable provides a stream of changes on the control, triggered by the user or programmatically .

What does programmatically mean? This means that calling FormControl.setValue will trigger an emission to its subscriber valueChanges . This can sometimes lead to unexpected results. Imagine the following case: a directive binds to every [formControl] , injects a reference to NgControl , reads valueChanges0 , removes all spaces from it and sets the value on the control. For example:

This directive does exactly that – erases all spaces in inputs. But there is one thing: As soon as the user enters anything, it will click the directive, which will set a new value. So what will happen? Can we get an error soon after?

This can be easily avoided with an option called emitEvent , which defaults to ‘true’. When it is ‘false’, it basically tells FormControl that the value must be changed, but subscribers are not notified of the change. Here is how it works:

Note: Be careful with this – consider that if you value a value while emitEvent: false subscriber will not receive a notification.

Conclude

Angular Forms are very powerful tools that can be used to perform very complex operations. But to control and master it is really difficult, and this article helps solve some common problems related to them. Hope it helps you a little bit in the process of working with Angular. Thanks for reading the article

References: Angular Forms: Useful Tips

Share the news now

Source : Viblo