Custom decorators in Angular applications

Preface

I am developing web applications on Angular for more than 6 years now, and due to my considerable experience, I have gradually moved away from coding typical features, such as laying out a form and sending data to the back, to more global and important aspects of web application development – architecture. Quite often I have to do both a regular code review at the application development stage, and I may be called upon to evaluate the quality of an already written application. And recently I was looking at the code of another product and noticed something that I would like to share with you, colleagues.

The functionality throughout the application is as follows – on each page, which is responsible for displaying lists of certain entities (let’s abstract), there is almost always a form responsible for displaying detailed information about the selected entity from the list. This form has typical buttons with action options: “Cancel” and “Save” the changes made. The logic is simple, if you haven’t entered data into the form, the “Save” button is not active. The data has been entered – the “Save” button is active. The developers, naturally, used the attribute disabled and as you probably guessed, we interpolated it and wrote into it the result of fulfilling a number of logical conditions. And everything would be fine, but sometimes it looked like this:

<p-button
(click)="createOrUpdate()"
[disabled]="someControlVeryLongName.invalid || !someControlVeryLongName.dirty || (loading$ | async) === true || (subloading$ | async) === true"

At first glance, we see only one problem – a very big expression for such a small “person” as disabled. But the problem does not end, but only begins in this component and everywhere else where there is similar logic. The point is that the attribute disabled skips event click in any case… This means that we are forced to drag this check into the component, and in this case, into the method createOrUpdate. The second problem (inconvenience) is threads: loading$ And subloading$. In the method we must combine them with the values dirty And invalid control and if all is well, allow data to be sent for updating or creating some entity. The third problem is that all the logic is duplicated everyone components in the application – DRY.

Solutions

Of course, it would be possible not to attach such importance to this problem (it works – don't touch it), but I developed a “sporting interest” in how it can be “beautifully” solved.

The first thing that comes to mind is to transfer the logic to another mechanism, which Angular literally teeming. At first I thought about the directive. What if you create an attribute directive and simply attach it to buttons where you need to track the state? disabled from the conditions I need? Since the project uses state-management NGXS – getting streams from it inside a directive is not a problem. But what to do for the condition invalid/dirty which directive should pay attention to? Moreover, on some pages it is FormGroupand on others FormControl. Knowing the hierarchy of controls (all forms in the application are written using reactive forms RF) I understand that these flags are available AbstractControl, and it is basic for FormGroup And FormControl. All I have to do is simply paste the desired control into the directive, and make the input parameter like AbstractControl. It would seem that the problem is solved, but… If you pay attention to the button control – this is an element from UI-Framework PrimeNG. He doesn't have API, which will help me influence the parameter disabled. Even if there was, I would have to use dirty-checking (force call detect changes), and I'm not a big fan of doing that. This is what the directive would look like:

Are there other ways? I’m sure there are, but I don’t know how effective and voluminous they are. And then I thought about decorators…

The great and mighty Angular

In real projects written in Angularas a rule, you do not need to write your own decorators, and if you do, then most likely you create your own library to simplify the “life” of fellow programmers. Angular has many build-in decorators for all occasions (Input, Output, Component, Directive, Optional, Inject and many others) and there really are enough of them, and their writing is nonsense. But in this situation, I did not see a really good solution from the basic or typical ones. Agree, it would be convenient to have a tool at hand similar to this one:

View-model component:

@Disabled(...)
public disabled$!: Observable<boolean>;

View component

<p-button [disabled]="!!(disabled$ | async)" (click)="createOrUpdate()">

Looks cool to me.

Decorators

If you have already dealt with TypeScript – you know that decorators are not a feature Angularand a feature of the language TypeScript. It allows you to change the entity it is applied to at a specific level called Reflection. This level is designed to work with types and their parameters (descriptors). It is important to remember that a decorator is a regular function that is called (applied) to an entity. There are 4 types of decorators:

  • Class Decorator;

  • Class property decorator;

  • Class method decorator;

  • Method (function) parameter decorator

More than one decorator can be applied to each entity. In this article, we will use a real example to look at the use of a decorator. the second type (property decorator).

Writing a Decorator

Any writing of a decorator comes down to one thing – a description of the function that should apply “decoration” to the target entity. That is, add the necessary fields/methods according to your rule, and the entity will use these utilities.

First, remember that for every component that needs functionality, by definition disabled for the button, there is always AbstractControlas well as the threads on which the state depends disabled.

Attempt at writing:

At first glance, some may think that this is “non-Hogwars magic” and I would like to go into a little more detail and clarify that there is no magic or witchcraft here. When we decorate a property/method/attribute/class with our decorator – our decorator is called! (Like any other decorators) And at this moment we can pass any setting for its further operation, as in a regular function. In this case, I just want to pass the name of the control in the component on which the state depends disabled my button. Function Object.defineProperty just helps me declare a property in the component with the same name to which my decorator is applied. In our case like this:

  • target – the component in which the property will be declared;

  • key – name of the new property;

  • descriptor – descriptor. It is he who describes how it will behave and how our new property will be accessible from the outside.

The third parameter of the function (descriptor) parameters enumerable And configurable says that the property is accessible from the outside for iterating (for example, for a for…in loop) and can be configured, since we can apply other decorators to the current new property. And of course the method get. That is, what to do and what to return when someone tries to read our property. This is where we build in our logic to get the final state of our button.

It’s not for nothing that an anonymous function was used here, and not an arrow function, so as not to lose the context. The context will ultimately be our component and it is through this we get all the data we need to determine disabled.

View-model:

@Disabled('agentNameControl')
public disabled$!: Observable<boolean>;

View:

<p-button [disabled]="!!(disabled$ | async)" (click)="createOrUpdate()">

Decorator improvements

Of course, we may make a mistake in specifying the name of the control, and also checking threads may be redundant or, on the contrary, their number may increase. To constrain the key that is passed in, I will use the operator as the first parameter of the decorator keyof, and I’ll move the flows into an additional parameter of the decorator and slightly add to the logic for determining the state disabled.

View-model:

Conclusion

As I said earlier, writing decorators in Angular applications this is nonsense, but sometimes they can really help improve the code. And of course, we all understand that this decorator can be improved endlessly. I hope this article was useful to you and perhaps it will show you a different side of decorators, or at least you will have such a solution up your sleeve for the future.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *