Angular + NGXS + Normalizer: Better Data Management With Normalization

In this post, we’ll explore a better way to manage the ngxs stores using data normalization.

Ignacio Ruiz
Geek Culture

--

What is data normalization?

Data normalization is nothing else than modelling the data in a “normal form”. But, that’s not really a thing, is it?

When we talk about data normalization in the state management pattern we mainly talk about four key points:

  • Every entity is represented by a single “table”.
  • Every item of each table is indexed by its id, just like in a map.
  • Any reference to other entities inside an entity is done with the referenced’s id (the foreign key, if we talk in SQL argot).
  • An array of ids is also stored for each entity to express order and to help us in the denormalization process that will be addressed later on.

You can think of data normalization as the way data is usually stored in relational databases. You don’t store a SQL entity inside another one, you create a pointer to the referenced entity, don’t you?

Let’s see two different sets of data, one normalized and another one that isn’t.

Version bump

You can see here an example of an structure that you have seen many times on the responses of APIs.

Each Pokémon contains the information of its type nested, and each type contains even more types nested representing their weaknesses and resistances. This is the same data, now normalized:

Ok, hopefully you can see a couple of wins we’ve achieved with this. But…

Why do we need to normalize our data?

These are the main advantages and they are indeed important enough for us to take the time to do this -not trivial- process:

  • Data is not duplicated : Have you seen how many times was each Pokémon type defined in the previous example when the data was not normalized? Duplicity on the data comes with a bunch of problems when trying to keep it updated and coherent. Also, memory is reduced!
  • Data is accessible with no computational cost: In the example, we don’t have that many nested levels, but imagine a real data set. If we want to update a n-level nested item, we have to find the index of the element on each level by iterating through them all; leaving aside the messy code that generates when accessing the data.
  • Only a small portion of the store’s tree gets updated on the event of a change: This is also important, since you probably have quite a few components listening for changes from the tree. If you update an object nested on another object, the listeners of the whole parent object will be notified. That won’t happen if the data store is normalized.

How should I do this on my NGXS store?

When I first faced the issue of having a deep nested state and tried to solve it by normalizing my data, I found articles on how to do it with React Redux and with NgRx. But since I love the simplicity of NGXS, I wanted to keep using it. So here’s my proposal on how to do it.

We are going to make use of a well known and robust library called normalizr which will, basically, normalize and denormalize our data.

First step: Create states that will hold our normalized data

In my approach, every entity is held by a different state. Of course, this would also work having a single bigger state, but in order not to have a single huge file (since ngxs does not allow us to implement action listeners in a different file as far as I know), I rather keep them separated.

First, to use normalizr, we should create the schemas for both Pokemon and PokemonType entities to tell it how to normalize and denormalize our data (if you don’t know how to use normalizr, please, go to the documentation section of their GitHub):

As you can see, we have defined here the normalizr schemas and some interfaces to make the state code a little bit more type friendly.

Now, both states hold the data in the way that it was explained in previous sections:

Nothing fancy here. The usual way to declare states on NGXS but the state model is an object prepared to hold our normalized entities indexed by id and an array of all the ids.

Second step: Normalize data that comes from external sources

Let’s suppose that a component dispatches an action that the PokemonsState will handle and it will retrieve the data from the API using a service injected into the state. The data that will come won’t be normalized and we want to convert it before storing. Also, the normalized data is stored partially on the PokemonsState (the Pokémon entities) and partially on the PokemonTypeState (the Pokémon type entities).

To achieve this, the action handler that will retrieve the data from de API will first normalize the data and then dispatch an action that both states will handle (some kind of a broadcast). Let’s see it in action.

Let’s focus first on the PokemonsState since it’s the one doing the most.

The first action handler, the one called getPokemons is the one in charge of getting the unnormalized data from de API. Then it will invoke the normalizePokemons() passing the retrieved array of Pokémons, this one will make use of normalizr to get the normalized entities. And, finally, it will dispatch the broadcast action, GetPokemonsSuccess.

The GetPokemonsSuccess handler will merge the entities that are the stored in the state with the new ones.

Now, let’s see the PokemonsTypeState:

Well, this certainly looks familiar. It basically does the same thing than the PokemonsState.

And here is the result of the normalized state on the redux devtools, as you can see, we have achieved a normalized data set!

Redux dev tools with our data normalized! Yay! 🎉

Final step: Denormalize our data to make use of it on the components

OK, the normalized data is perfect to store it and to make changes in it(not in the scope of this article, but you probably can see how easy is for the action handlers to modify the “nested” data).

But, we are developing an Angular app, the final actors in all this are the components and, believe me, handling a normalized data set in a component is not really friendly. We want to denormalize it!

Who will be in charge of this? The selectors.

I will just show here an example of a selector that will select all Pokémons. It will include the type information, of course.

To keep things clean, we will make use of the selector combination feature that NGXS has.

First, we have a selector on the PokemonTypeState that will give as a “raw” set of normalized data.

That was easy.

Now, we will join that selector with the real one that will be selected on the components.

Here is where denormalize “magic” happens. But it’s not that fancy, is it? We get the entities from both states (to denormalize we need them, as expected) and the normalizr library gives us our original object… or not?

This is the result with this code.

Infinite circular dependency! 😨

As you can see, normalizr doesn’t handle circular dependencies as we expected and we we’ll reach a memory limit. We have to manually stop the cicle.

That we do by modifying a little bit our schema:

With this, the types inside weaknesses and resistance won’t have another types inside, so normalizr won’t denormalize them. Let’s see the result:

That’s better 😌

OK, we can work with this! Well, our “inner” types still got a weaknesses and resistances array, but inside of them, we can only find ids, so the recursive unwrap is stopped.

You’ll get this data on the components and you’re free to use it as you wish.

That’s all, I hope you enjoyed the article and, please, leave any suggestion on the comments section!

Talk to you!

P.D: You can find the final version of the source code here.

More articles on the matter:

--

--

Ignacio Ruiz
Geek Culture

Web, mobile and videogame passionate developer. Always looking for the best practices!