Reactive programming in Angular: Reactive components (Part 2)

In my last article, I spoke about the idea of having some type of isolated component whose evolution, does not affect the application. With the example of a small table, we tested the redux architecture implemented with the ngrx libraries and we concluded that it is not a valid solution for our problem because ngrx creates a global context that leads us to code a monolithic application. In this article we are going to try another alternative to see if we can code an isolated real component.

Adrian Ferreres
ITNEXT
Published in
16 min readJul 25, 2018

--

What do we have until now?

This is my third article on reactive programming in Angular. The first one was “on push communication”, where I explained how to use rxjs to avoid unnecessary rendering events. The second one was “Reactive components (Part 1)” where I expose the problem of isolated components in the code. This article is a direct continuation of the previous one so, if you have not read it yet, I advise you to do it before continuing with the next section.

The problem of the store

The first reason why a redux implementation becomes a monolithic application is because the global store. The redux store is like a great global variable, each component can read and write it. That means that the reading components are dependent on the writing components. In our example, it happens with the table and the card. The card needs that the table and the router update the global store to obtain the information of the selected person. Without them, the detail view can not be rendered.

To avoid a strong dependency between the components, we must transform the global store into a local store for each component. In this way, write / read operations for each component are performed locally and there are no additional dependencies between the components.

A local store can be implemented in a really easy way with BehaviorSubject. A subject is an observable that can also emit events and a BehaviorSubject is a subject that always returns an event when it is subscribed. It is ideal to implement a store because, when a subscription is made, it always returns the last issued state and, if no state was issued, returns the default state.

So, the first thing we’re going to do is go back to the initial example of the table application to add a new attribute called “state$” which will be our BehaviorSubject store.

Is it a component? … Is it an observable?… No! It is a reactive component

Having the store as a local is fine, but it can be a problem if we want to share information between the components. Returning to the ngrx example, it is true that the global store creates dependencies between the components, but it is also true that the card component needs to read the state of the table to obtain its own data. So, the question here is how can you upgrade the card component without a global store. Well, from my point of view, the answer lies in the pure concept of redux architecture.

The only thing that can change the state of the view is an action. As each component has its own store, each component must have its own actions to update its status. In other words, instead of the card reading the state of the table, the selection of the table must activate the event to update the component of the card. The main idea is that the components share information through chained events; like the pipes in Unix commands where the output of one execution is the input to the next.

One way to implement this solution could be to create a service that wraps a Subject object and that allows emitting and subscribing to the actions performed by the component … but I do not like this solution. The service would be a 100% boilerplate code because it only exposes the existing api of the Subject and I am not interested in increasing the already huge boilerplate that each redux implementation has. So, instead, what we are going to do is transform our components into reactive components, extending the subject object.

It is common, among the Angular developers, to forget that the components are TypeScript objects (or JavaScript objects … depending on how strict we are) and how objects can be extended by other objects. We also frequently forget that these objects can be recovered by the “viewChild” decorator and, then, all their attributes and methods can be accessed just like any other object. Therefore, if our components extend from Subject it means that their events can be channeled among them to create reactions where the actions can be sent from one component to another without the necessity of having a global store. This is the real power of the reactive components.

The other problem; the effects problem

Removing the global store is not enough to eliminate the links that transform our application into a monolithic application. We have to eliminate the effects, too. The problem of the effects is that they presuppose information that the component does not have at the moment in which the object is encoded.

Going back to our table example; The component of the table should not know if your data is from people, dogs or buildings, it should only worry about drawing a set of columns based on a configuration. However, the effects of the component must carry out the data requests which oblige us to link the component to a specific type of information.

Similar problem with error handling. A table with a list of people can be read by an HHRR expert or by an IT administrator and we should not show the same error message in both cases. In the case of the HHRR employee, we should present a general error message because it is likely that he or she does not understand the details of the error. On the other hand, an IT administrator will need more details because he or she could try to resolve the problem. The fact that the effects handle the error messages of the data requests within the component takes away flexibility when it comes to reusing the component in different contexts.

In summary: The main problem of the effects is that we can not code them without thinking about the context of the application (the users and the data that the application will need at the moment in which we are coding) and makes the component not reusable in another application or at another time (the data and the user may change in the future).

Inner and outer actions

When analyzing what the effects do, I think we can classify the actions into two groups: internal actions and external actions.

The internal actions are the actions that can be handled with the current state of the component and the data of the action. That type of actions can change the current state and update the view because they do not depend on any other context, there is no possibility of any side effects. All internal actions are predictable.

External actions are the actions that ngrx handles with the effects. This type of actions that need data from the back or communication with other components. These actions can not update the component status because they can have unpredictable consequences. However, they can execute internal action once their result is safe.

One thing that internal and external actions have in common is that both are defined within the component, but only the internal action is handled in the component. External actions are like the interface of the component, define the inputs and outputs of data. The way it works is simple; As I explained before, our components are also observable and can be subscribed by the component that uses them. In this way, we can filter the events emitted by the reactive component to obtain, only, the external actions. The actions can be linked with an http request or with another reactive component. In that way, we can define chains of actions.

Again let’s take the example of our table application; the home component gets the instance of our table component using the “viewChild” decorator and creates three subscriptions on the table. The first to obtain the external action “LOAD_CONFIG”, the second for the external action “LOAD_ROW” and the last one for the external action “ROW_SELECTED”. The first two are data requests. Each of them is piped with the ApiService methods to obtain the necessary data in each case. At the end, in the subscritions, the internal actions “LOAD_CONFIG_SUCCESS” and “LOAD_ROW_SUCCESS” are issued with the configuration data and the data of the rows, respectively. The “ROW_SELECTED” ends with a navigation to the details web page.

As you can see, this approach is closer to a stream configuration than a classic code programming (I have already mentioned that these articles are about reactive programing… Haven’t I?). We treat the components as Unix commands that have their inputs and outputs and can connect to each other using the rxjs library.

Less talk and more demos:

Now it’s time to leave the explanation to jump to the example:

In the same way as before, let’s analyze what is happening in the table every time we request a new page or select an element.

Request a new page:

  1. When the user clicks on one of the pagination buttons the method changePage of table.component.ts is run (src/app/table/component/table.component.ts lines 68–80)
  2. This method fires an outer action “LOAD_ROW” (src/app/table/component/table.component.ts lines 73–80)
  3. The home component is listening for “LOAD_ROW” events (src/app/home/home.component.ts lines 53 -65)
  4. The previous observable is piped to the api service method to get the new table page (src/app/home/home.component.ts line 57)
  5. If everything goes fine, we get the answer from the back with the new data. (src/app/home/home.component.ts lines 60–65)
  6. The data obtained from the service is converted in table data using a facade service (src/app/home/home.component.ts line 63)
  7. With this information the home component emits the inner action LOAD_ROW_SUCCESS (src/app/home/home.component.ts lines 61–64)
  8. The table is also subscribed to its own events (src/app/table/component/table.component.ts lines 46- 48)
  9. Each event pass through the reducer and, the resulting state is emitted in the local store (src/app/table/component/table.component.ts line 47)
  10. The reducer creates a new state and updates it with the information of the action (src/app/table/reducers/table-reducers.service.ts lines 8–23)
  11. The store is subscribed in the table template through the async pipe (src/app/table/component/table.component.html lines 1, 4, 8 and 9)

An important annotation: the table component emits the complete signal for the store and itself on the destroy hook. In that way we are avoiding the memory leaks and make unnecessary to handle the unsubscription in the app.

Select an item:

  1. When the user select a row from the table the method “onSelect” of the table component is run (src/app/table/component/table.component.ts lines 94–100)
  2. This method emits the outer action “ROW_SELECTED” (src/app/table/component/table.component.ts lines 96–99 )
  3. The home component is waiting for outer event “ROW_SELECTED” (src/app/home/home.component.ts lines 67–73)
  4. In the subscription, the home component use the index in the action’s payload to build the url at the details web page and navigate to it (src/app/home/home.component.ts line 71)
  5. The details component uses the index in the url to create a new observable and to get the details of the person (src/app/details/details.component.ts line 20)
  6. The subscription is made by the async pipe in the details web page (src/app/details/details.component.html lines 5- 7)

But… Does it solve our initial problem?

We can not forget our initial objective. We want to codify a component whose evolution, does not affect the rest of the application. To test if the reactive components are the solution we are looking for, I updated the table with the drag-and-drop functionality to sort the rows. This is the example:

Let’s check the step I followed to add this new functionality:

  1. I defined two more inner actions “DRAG_ROW” and “DROP_ROW” (src/app/table/entities/table-actions.ts lines 12- 13)
  2. In the state of the table I created a new attribute to store the row that the user is dragging (src/app/table/entities/data.ts line 9)
  3. In the reducers I defined the state changes that the new action will perform (src/app/table/reducers/table-reducers.service.ts lines 20–29)
  4. I added in the table template the events for drag and drop (src/app/table/component/table.component.html lines 10, 12, 13 and 14)
  5. I added in the table component the methods which fire the actions when the events of drag and drop are happening in the template (src/table/component/table.component.ts lines 102–118)

As you can see, the addition of these new features does not affect the application at all. While the component is working with the data of its local state, any other change is unnecessary. Of course, this new functionality is useless in a real application because to maintain the order of the rows we must send the new positions to the back end. In this architecture, that means a new external action that implies a new subscription in the website where this feature is used. End of story. Nothing more. The templates do not change because this new event is issued by subscription. The other pages that do not allow drag and drop are also not affected.

Another point to consider is what would happen if we changed the structure of the payload in external actions. In that case, the facade would be affected. The facade is the service that translates the objects of ApiService to the objects of the components (src/app/facade/table-facade.service.ts). It is the main point of connection between the application and the component, and its mission is that both the services that recover the data from the back and the components that process the data do not depend on each other. So, basically, a new structure in the payload means a new translation on the facade. It happens in the same way when a web service changes its interface; it affects the front-end services that make the http request. We can not avoid that, but we can reduce the impact of these changes by using a façade service to centralize all translations between the data returned from the back and the information that the components need.

A stateless application

The best advantage of the reactive components is are the possibility of treating the information as a data flow. We saw it in our last example of the table when we requested a new page on the back. Everything resides in the connections. The external event is piped with the http request and, the result is channeled back into the same table, but there is more. Using the same technique, we can pass data between different components (of course, because all the components are Subjects). So, imagine that now we do not have a detail web page, the card component is just another component that is under the table in the template. The way to implement this change could be something like this:

As you can see, here, the external event “ROW_SELECTED” of the table is sending the information of the selected row instead of the index of the row. This event is piped with the internal event of the new card object, “UPDATE_INFO”, which shows the data inside the card.

So the question here is are: where is the information?, where is the source of the truth? The information resides in the events. It’s like your bank account. When you check the balance in your bank account and see that it is wrong, you do not go to the balance again to see what is wrong, it goes to the transactions that are the entry and exit events in your bank account. Here is the same.

An application that uses reactive components does not store any information, does not have any state. It only provides a configuration of connections between the different Observables. Components with services, components with components, services with other components, etc. So once you code a set of reactive components, you can have as many applications as possible combinations among the different Observables.

We saw it in our examples. At the beginning we had an application with two web pages; one for the table and one for the details. With only a few lines of difference, I was able to change our previous application to one with just one page with the details and the table in the same view. And the more reactive components we have the greater the possibilities. It is not magic. It’s just reactivity.

A change of philosophy

If we stop for a moment and begin to think about why we code a component, the answer will be that we want to save the work. That is, once we look at the requirements of the application and we have the model, we can distinguish which elements of the application are repeated and turn them into components. And this is when we start coding a monolithic application. The problem is that our components are a side effect of the application, so, in the end, they become part of the application. We codify them thinking about what information they should present, what kind of people would use them, what messages they had to show for the application, in other words, we encoded them according to the context of the application.

If we want to code a component-based application, we must change the roles between the application and the components. The application must be a consequence of the components; not the opposite. So, if the application needs a table, our main objective should be to code a table and only one table. Nothing else. Once we finish with the table, we will jump to the next component; a menu, a carousel, a drop-down box … it does not matter if the component is used only once in the application. We are coding reusable components in several applications, for sure we will have another opportunity to reuse it.

Once all the components are coded, the app appears when we pipe them with the services or between them. In that case, the app is stateless. Its mission is to connect the streams of events between the different reactive components. The real application are the components which have their own logic, with their own state machine, with their own contracts.

This change of opinion is very important if you want to code a component application because, even with reactive components, nothing can prevent you from coding a monolithic application with the exception of yourself. Imagine that I add an http request to get the data in the table component that I used as an example. If I did, I would be linking the data with the component and goodbye to reusability. Therefore, it is really important to try to abstract the thinking from the entire context of the application before to begin coding the components.

I am afraid that we still far from the definitely solution

As I said in the previous article, the perfect solution does not exist. Therefore, true to my principles, the time has come to explain what kind of problem we will face if we choose the reactive component as the architecture of our application.

I met some people who say that, in their opinion, ngrx is not a solution for beginners. The reasons that they give are that, once learned Angular, to use ngrx in a project, it must study all its ecosystem (store, effects, reducer, entities) and how to use them. Well, believe me when I say that the architecture I propose here is even more difficult. Once you have learned to use ngrx, all things are done in the same way. Ngrx provides a clear route to encode any feature and forces us to do so using simple and small codes that are easy to read and easy to test. In addition, as ngrx part of a context, the possibilities are limited and it is more clear what are the requirements that each component must meet.

With the reactive components, we are coding some kind of “universal component”, so there is no clear and unique way to do it. The requirements are more general and requires more experienced developers to code them.

Another disadvantage is the performance. I have the opinion that if the performance problems in a web application reside in the response time of the http requests backwards, the performance problem is not in the front-end. However, I have to admit that one of the actions that has the greatest impact on the user experience is the waiting time between a pending request and the response. The characteristic of memorization and the fact that there is a global store that shares information throughout the application, reduces the number of times it is necessary to request information on the back so that the overall performance of the application is better.

In the end we are in the same dilemma as when I presented the example of the two functions that add 4 and 6 in the last article. Monolithic applications are easy to code and should have better performance, but the applications of components are faster to code (once you have the components encoded) and are easy to maintain because they are more resistant to breaking changes.

This ends but I will continue

In my personal opinion, reactive components could become one of the most useful ways to code components in the short term. We just have to look at what Angular Elements offers in this regard. The reactive components offer the possibility of encapsulating view and reusable logic in a library that can be reused throughout all types of applications with Angular … but applications must have Angular so that we can reuse them. With the appearance of Angular Elements, this universality can be shared with all JavaScript applications regardless of the library / frame with which they were encoded at the beginning.

I spent a few weeks playing with Angular Elements and I think that it is not yet ready for the production environment. Nowadays, It is in alpha version, but I am sure that the team and the Angular community will fix all the issue successfully. I really want to see what we will be able to achieve when we combine reactivity with Angular Elements.

With this article I close the topic of the reactive components but not the reactive programming in Angular. In the next month, I will write how to use observable to code a centralized library to manage events throughout the application.

To finish, I would like to say that I am quite happy with these two articles, regardless of whether they are successful or not. I answered several CoPs in different events to make a talk about the reactive components and I always got in response that the topic was difficult to understand. Certainly, I am not the best summarizing and here is the proof with these two extensive articles, so I can understand what my mistakes were at the time of writing my proposals for the events :). Now that I have been able to explain myself in detail, I hope I have contributed with new points of view that can lead the community to new debates on how to codify completely reusable components in Angular.

--

--