Declarative Data Manipulation with RxJS & Angular

Manipulate data using a declarative RxJS pattern in Angular

While both imperative and declarative programming styles can add value in the right context, delegating the execution of some code to the programming language can optimize timing.

By using an RxJS declarative pattern in Angular we can delegate the execution of some code to fetch data when it is required in the template.

As an example, let’s look at the following async pipe:

// app.component.html<div *ngIf="data$ | async as data">
{{ data.title }}
</div>

By using the async pipe, we don’t need to subscribe or unsubscribe from an observable. Angular takes care of it automatically.

However, as soon as the template is loaded, the async pipe gets the data and displays it as-is.

Often we need to manipulate the data before displaying it. To do that, we can use some RxJS operators to manipulate data before it reaches the template.

Think of a situation where you receive data from an HTTP call and want to manipulate it before making it available in the component.

As an example, we will use the HttpClient service to fetch data from a server. “The asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received”, angular.io.

In our example, the Observable contains an object with a specific shape, so we can create an interface as follow:

interface ToDo {  userId: number;
id: number;
title: string;
completed: boolean;
}

We want to change the value of the title key before displaying it.

Classic pattern

Following an imperative pattern, we would use the subscribe method to subscribe to the Observable, probably in ngOnInit.

Then we would assign the outcome to a variable and eventually manipulate the variable when we need it.

As an example, the following code assigns the outcome of the subscription to the subscription variable.

If we need to change the value of the subscriptionvariable, we would probably create a method that takes subscription and changes the title before it gets used in the template.

Declarative Data Manipulation

While trying to be declarative, you may wonder how to manipulate data without falling back on using the subscribe method.

Here is the initial declarative code:

Line 10 is the starting point.

We define a local property called data$ and assign the Observable from the service to the property to make it available in the component.

In case you didn’t read RxJS Declarative Pattern in Angular, be aware that at this point our code is not “executing” the Observable!

In other words, there is no network request related to fetching data from the server. There is nothing going on here. data$ is an empty observable.

Logging an Observable before any subscription or async pipe
Logging an Observable before any subscription or async pipe

The value of data$ will change when we subscribe to the Observable or when we use an async pipe in the template.

As discussed above, the former option goes against a reactive approach so we are left with the async pipe. However, as soon as Angular renders the component view, the async pipe triggers data$ and display whatever we get from the Observable.

So we need to manipulate data after we get it from the service and before it gets “called” by the async pipe.

To do this, we will use the RxJS pipe API and the RxJS map operator.

RxJS pipe

We could use pipe as follow:

data$ = this.todoService.todo$.pipe(  operator1(),
operator2(),
operatorN()
);

To put it simply:

  1. The Observable that we receive from the service enters the pipe().
  2. It goes through the first operator and gets manipulated according to a provided function that returns a new Observable.
  3. The new Observable enters the second operator gets manipulated according to a provided function, and so on until the last operator in the pipe.
  4. Finally, the Observable is stored in data$, once we subscribe to it. Otherwise, we just declared what we want to get but data$ is still empty until we use an async pipe in the template.

RxJS map operator

Following the code above, we will use the RxJS map operator to manipulate the Observable before making it available to the async pipe.

In short, we could say that the map operator transforms each emitted item by a function we provide.

It subscribes to an input stream, transforms the items it receives according to a provided function, and creates an output stream with the transformed items.

The RxJS map operator is very similar to the JavaScriptmap() method, and it can be used as follow:

data$ = this.todoService.todo$.pipe(  map((x) => ({ 
...x,
title: x.title + 'and more'
}))
);

The Observable we receive from the service enters the pipe, goes through the function that we provided in the map operator, and exits the pipe.

Inside the map operator, we declared an anonymous function, () => {}that takes an argument x and returns an object.

The returned object has all the key-value pairs of the object we passed in, thanks to the spread syntax, but we change the value of the title to add the string 'and more'.

To be more precise, we can even declare the type of the parameter xso that the code becomes:

Data Manipulation using the RxJS tap operator

You may want to declare the interface in another file to make it available elsewhere. In this case, it is easier to see it in the same snippet.

However, it seems the amount of code necessary to do some manipulation could increase quite quickly.

Therefore, there could be a point where the benefits of using a declarative pattern would be offset by the cost of “too much code”.

Too much code?

Declarative code is distinguished by its high abstraction level.

Developers can represent sophisticated patterns in a compressed fashion. However, the more complex the program, the higher the risk that the code gets so tangled that it can only be read by the developer who wrote it in the first place.

And this is not good.

Generally, if working in a team, we want to be able to maintain and build apps without relying on the knowledge of a single individual.

Furthermore, in a business context, it may be necessary to spend more time onboarding new hires or external developers so that they completely comprehend the code structure.

All in all, it might even increase your costs.

Leave a Comment