Request Management in Angular

Make your life easier with decorators.

Georgii Kuzmin
7 min readFeb 9, 2021
Photo by Tim Mossholder on Unsplash

Often, when writing an application, you have to isolate functionality to reuse it later. Basically, these are infrastructure things. If we talk about the frontend, and especially about Angular, most people have encountered separate API services, state managers, and global functionality. In this article, we will talk about a similar topic. Request managers and how to quickly work with pending requests in the app.

💭 Idea.

In my previous project, we often faced the need to display the user request status (spinner), which is not uncommon. But we did this every time with a different approach. The application grew as well as the number of places to show our ‘spinners’: tables, sidebars, popovers.

We can, of course, find lots of libraries out there in the wild and tame them to partially or completely cover the need to manage these processes, but this article is not about a specific solution, but a possible approach and experience.

The idea is to write a code that can be easily reused anywhere in the application, without additional sophistication and even without injecting additional services! The simplest and most beautiful way turned out to be decorators.

🔍 Investigation.

For those who did not write their own decorators — an introduction to the course. By themselves, typescript decorators are compiled into functions, so they do not contain any forbidden magic, and it is possible to write a decorator of any complexity, as far as your imagination allows. The function takes 3 arguments:

⭐️ the class in which it is called,

⭐️ property key — the name of the field over which it is called (this is both the method and the field, we will analyze the example below),

⭐️ descriptor is an object that contains “metadata” about a field (which is described by Object.defineProperty function), whether this field can be changed, its value, whether the field is enumerable, and so on.

With this in hand, it is possible to change our class or field so that during instantiation, the field or class contains the necessary information. However, the decorators are not executed inside our Angular application, which means that we will have to link them… and we managed to do this via a static field in the injectable service.

🧩 Realization.

Let’s start with how we will link Angular and decorators. To do this, you will have to prepare two services. The first is the stateful service, which will store the state of our pending requests, and if necessary, it will be possible to inject this service anywhere and use it directly. The service will manage the flow of the process, add and remove the pending flags on requests.

Stateful service, that contains all pending or completed requests.

It looks like a standard service in Angular with a private behavior subject which contains an object where the keys are the names of pending requests, and the values are the state — true (pending) or false (complete).
Also, you should add 2 fields here:

⭐️ status$ — returns the entire object with requests.

⭐️ isLoading$ — returns true or false flag.

And there are two methods whose purpose you can easily grasp from their naming. The first method adds a pending request, the second sets request to false.

If it is clear what happened to the stateful service, it is quite muddy to notice how to use this service in decorators, because they are simple functions that are executed outside of Angular’s scope. The solution is to use a class with a static field. The idea is to create an injectable which, at the first initialization, receives our LoadingService as a dependency and sets it to our class static field. Here’s our abstract injectable:

Injectable service with a static field.

You have to make an instance of StaticLoadingService. To do this, you need to inject it into the module with our services.

Inject StaticLoadingService to make at least one instance.

To be honest, it looks like a dirty trick that should not be used, but the same approach is used by most libraries with decorators. The only advice, hide it from junior developers, the immature psyche is not ready for this 😄

Finally, let’s move on to the decorators themselves. Let’s start with the decorator on the method. The plan — we agree that the method must return completable observable with error handling in case if it is not. We will set the true flag in our LoadingService during the subscription to observable and remove it on finalize. As an option, you can use “tap”, but the idea is to handle HTTP requests, which are just auto completable observables, so we’ll focus on them.

To run our loader only by subscription, you need to write another rxjs statement:

The operator that executes callback only on subscribe.

We use the “defer” function from rxjs. “Defer” on subscribe executes the function passed to it, which returns observable. Let’s call our callback function inside.

And the decorator itself:

Loading decorator for the method that returns observable.

What’s going on here, looks creepy at first glance. The decorator itself takes a string as an argument that will become the name of our request. If not, it will collect the request name from the method name + the name of the class in which this method is declared. (10th line)
We have to keep the original method. We take the function from the description object passed by typescript to the decorator as the third argument, from the value field. (9th line)

Configurate loader name and save the original function.

Since the value field in the description object is a direct reference, you can rewrite it this way:

Rewrite the original function with our functionality.

I have to note that you need a context function, instead of an arrow function, in order to get the desired object instance as a context during execution. We call the original function, with the necessary context, and repass the arguments. In the pipe, we use two operators:

⭐️ subscribeCallback — a written operator that invokes a callback during a subscription.

⭐️ finalize — the classic rxjs operator, executes callback on observable completion.

Inside these operators, we call the methods from our StaticLoadingService on subscribe — setLoading, on finalizing — removeLoading.We also throw an error if the method did not return observable.

The first decorator is in the sleeve ✅

The second decorator wraps the field in which the isLoading$ observable will be. The plan is more complicated here, we will have to assign a field with the same name to the class inside which our IsLoading decorator is located, over which the decorator is called, and define there a getter with an observable from the service.

Also, we will define another field in the class where we will immediately put our modified (or not) observable, so that in the future when accessing our getter, we do not have to build observable every time.

The screenshot will be clearer (I hope):

IsLoading decorator for the property.

Let’s take it in order. The decorator takes an array of strings as an argument to filter out the necessary pending requests. If the array is not passed, the global status will be passed, i.e., true if at least one request is pending.
To do this, we put a field with observable in our class, which we take from StaticLoadingService, which takes it from stateful LoadingService.
If the array is passed, we filter status$ to the desired request names (17th line), if not, then pass isLoading$ (21st line).

Now we have the second decorator ✅

Everything is ready to use.

Component example.

As you can see this approach significantly reduces the quantity of code you have to write to observe requests status or any other completable observables. Moreover, decorators are powerful in such cases as combining and reusing code, managing any global state, etc.

Good luck, and may your apps always score a hundred points in the lighthouse.

Full code you can find here.

--

--

Georgii Kuzmin
Georgii Kuzmin

Written by Georgii Kuzmin

Senior software engineer. Snowboarder. Tennis player. Gamer.

Responses (2)