Data flow from API to the DOM: unexpected, unpredictable bugs of the naif implementation

In order to know what we are doing and analyse the new implementation, we need to have at least a general idea about how change detection algorithm works.

I am not going to go much into details: if you want a deeper read, I suggest you this great article about change detection in general, and this other one about DOM interpolation.

Change detection uses a recursive algorithm:

The algorithm starts at the root component : the first thing it does is recalculating and updating the “@Input()” bindings on the component

: the first thing it does is recalculating and updating the “@Input()” bindings on the component It then extracts the list of children components, and updates “@Input()” bindings on each of them

The algorithm keeps doing the same thing recursively, until it finds a leaf component (a component without children components): on leaf components it passes to the next step, which is updating the DOM corresponding to their templates

(a component without children components): on leaf components it passes to the next step, which is corresponding to their templates It then comes back to the parent component and updates the DOM corresponding to its template

It repeats the same operation recursively, until it comes back to the root component where everything started: now the whole DOM is updated

Even if this was a very simplified description of change detection algorithm, it has something more than most descriptions you’ll find online, because it focuses on the recursive nature of the algorithm, which is often ignored.

In fact, recursiveness is the core of the change detection process, specifically the ability of deciding how to update a component, only based on its status and the status of the parent component.

When the algorithm is looking at a child component from a parent component, it usually faces three possible scenarios:

Child component only depends on the declared inputs, and those inputs are not going to change without their reference changing as well: this is the most favourable scenario, in which you can use “onPush” strategy and detach from change detection the whole child component’s subtree Child component only depends on the declared inputs, but those inputs are objects which attribues can change while still keeping the same object reference: in this case it’s not possible to skip change detection for the subtree, because Angular2+ might not realise that something in that subtree has changed (for performance reasons, Angular2+ only updates bindings when their reference changes) Child component doesn’t depend only on its inputs, because it additionally fetches data from the API or other external services: in this case as well optimisation is not possible, and change detection has to run for the whole subtree

Now we are ready to look at the specific case of our latest implementation; this is the template of “PostComponent”:

When change detection finds this template, can you yell in which of the three described scenarios we are?

It can’t be any of the first two, because the component evidently doesn’t depend on its declared inputs: in fact, the component doesn’t have any input, because its whole content is determined by a call to the API.

But the problem is that, surprisingly, we are not in the third scenario either, because the status of the template doesn’t depend only on the API.

We are actually in a fourth scenario, that didn’t seem to be possible when at first we looked at the change detection algorithm: the object “post”, that is used to populate the template, is not modifiable only by the API, but it can potentially be modified by any other service or component who fetched the same JSON from the API service.

In the code snippet above, “ApiService” caches the returned JSON in an instance attribute, which is good practice to avoid unnecessary external calls. But, since we bound directly to the DOM the object we received from the API service, our component is now exposed to trivial errors, like the one in “AddMisterToUserNameService”.

Components, that bind directly to the DOM objects received from the API, are not isolated, no matter how nice and well designed is your DOM model.

How can we prevent this problem and make our components isolated? Let’s step back for one moment, and ask ourselves another question: do we actually want our components to be isolated?

There are actually situations in which it’s good to propagate automatically everywhere in the DOM a change that occurred in a model; but, if we are in such a situation, we should decide intentionally to propagate those changes: we shouldn’t do it as a side effect of binding to the DOM an object returned by the API adaptor, just because it’s easy.

In our specific case, especially because we are looking at a standalone example without knowing the context of the rest of the application, we definitely don’t want to be able to update the object “post” globally: so let’s look at possible solutions to achieve encapsulation.

If we want our component to be isolated, we should either copy in the DOM model every attribute we need, or make the API adaptor copy the whole object before retrieving it; if we choose the second option, the best way to achieve it is by wrapping the API data into a Front-End model.

If I didn’t talk yet about Front-End models, it’s only because I really wanted to put the stress on the correct architecture design flow, that should always start from the DOM model, and, subsequently, abstract Front-End models; in fact, you start needing Front-End models only when you connect the different parts of your application, and standalone examples like the one we are studying can perfectly live without any abstract model behind them. Anyway, I’ll dedicate some words to Front-End models before the end of this article.

Look at the constructor of the Front-End model class “Post”:

It copies every relevant property of the JSON into a local attribute, so every time we instantiate a new “Post” object we get a different copy of those properties

of the JSON into a local attribute, so every time we instantiate a new “Post” object we get a different copy of those properties It filters out all unnecessary properties that might come from the API (we are ignoring redundant data)

that might come from the API (we are ignoring redundant data) I left the JSON type as “any” because that JSON is something that comes from the API and not under our control

Let’s now look at the other potential risk of our naif implementation of data flow from API: the fact that the array of replies is bound to the parent “CommentsComponent” and the array of comments is bound to the parent “PostComponent”.

Obviously we have the same non-isolation problem we remarked for the “Post” object, because those arrays both came directly from the API adaptor service; but in this case there is also an additional risk, because the arrays can be accidentally modified from parent components themselves:

In the code snippet above, we forgot that the “post” object has already a property called “comments” (because such component has not explicitly declared anywhere, since we injected it directly from the API), and we accidentally use it to store other things. The side effect of this error is that we’ll accidentally wipe away all the HTML related to comments and replies (and possibly even break the app).

Probably this specific error is not so realistic in a real app, but it serves for my purpose: I wanted to stress how our nice DOM model is unexpectedly weakened by the way we import data from the API.

With the architecture we designed, we made “PostComponent” responsible for DOM rendering of all comments, replies, and any other child component as well: as in the previous example, we have to ask ourselves if we really wanted to achieve that, or if we did it accidentally because of the data structure that came from the API.

While there are definitely a lot of cases in which we want a subtree of components to be completely modifiable from the root component (in an admin interface for instance, where we want the user to be able to sort items at any nested level), we definitely don’t want it here: a user shouldn’t be able to modify comments and replies by performing actions on the “post” component. All subcomponents we designed should rather be isolated.

I think that there is a deeper problem behind architectural errors like this:

People often make confusion between the processes of FIRST RENDERING and UPDATING the DOM.

The fact that, in order to render the DOM the first time, we need to connect comments to posts and replies to comments, doesn’t imply that those connections are actually necessary to make the DOM interact with the user.

The solution I want to propose for this problem deserves its own paragraph, so I’m wrapping it up here.

To conclude the paragraph, I’d like to summarise again in a few words the following important concept: we said since the beginning that, when designing the DOM model, we shouldn’t be influenced by the structure of Back-End models; in the same way, when we design the data flow from Back-End to the DOM, we shouldn’t be influenced by the data structure that comes from the API.