I will start this article by saying that State Management is not easy.
You might think "c'mon, state management is just a bucket that holds data. Not a biggie". And yes, I....agree(?) It's a bucket that we have to split in slices, to think what data to add and in which slice, how and when to get data from the slice, how to deal with the side effects, how to construct the actions and the functions that handles those actions, aka reducers. Well, it seems that it's not that easy after all.
In Angular, the State Management can be achieved in various ways.
In this article, I will focus on the way we handle the state using NGXS.
A 1000 foot view architecture
The main concepts of the NGXS are the following:
The Component dispatches an Action, the Action mutates a slice of the State and finally the Component selects a slice of data by using Selectors.
The Selectors return Observable types, and we can either explicitly subscribe to the component's source code or use the Async pipe in the template.
Every time the State changes, a new object reference is persisted in the Store. This results in making the change detection trigger a new cycle, getting the store's relevant data, and displaying them in the template.
Let's see a straightforward example. Yet another TODO application.
In the code below, we see the code of the component.ts
In lines 15-16, we get a slice of the data from the state, and we assign them in the items$.
In line 22, we have the add method, which dispatches an action and adds an item to the list. As soon as we click the button and the action is dispatched, the items$ gets the new list from the state.
In line 27, we dispatch an action whenever we want to change a current TODO item's value. Again, when this action is dispatched, the items$ gets the new list from the state.
This is the part where we have a problem, but we will address it in a while!
Let's first see how the template looks like.
In line 3, on the click event, we invoke the add method. This will result in having the new item on the list.
In line 8, we iterate over the items$ list, and we display the value of each one on an HTML input field.
In line 9, note that we invoke the changeDescription method on the blur event.
What about if we invoke the method on the ngModelChange event? We will have a "washing machine" effect. As soon as we type a single letter in the input field, the method will fire, the state will change, and the items$ will get the new list.
This results to lose focus from the input field because the list re-paint!
In the below short video, you can see the effect.
In this case, the problem is that we use the selector to get the data from the store and iterate over it. You might think that this is what we need to have, that this is the correct approach. As we see from the video above, this is not the behavior we anticipated. We should somehow break the cycle.
To solve this problem, we will introduce a FACADE pattern, which will bridge the communication between the component and the store.
The code below has the code of the service, which will act as the bridge.
In line 14, we have the method which is responsible for dispatching an action to the store. The critical point here is that we also get a snapshot from the state, and we keep it in the items variable.
We use the items variable to display the TODO items in the HTML template.
In line 19, we do the same. We dispatch an action to update an item in the state. The critical point here is that we DO NOT update the items variable. We do not have to re-paint the list whenever we have these kinds of changes. We broke the cycle, and we solved the problem :)
In the code below, we see how to HTML template looks like
In line 6, we use the ngModelChange, event and we do no longer have the "washing machine" effect.
The selectors offer a great developer experience and also maintain a clean code. It's not a panacea, though. Using a FACADE pattern provides flexibility and helps us overcome some issues.
You can find the code in this repo https://github.com/profanis/ngxs-todo. The solution with the FACADE pattern is in the branch with-facade
Thanks for reading this article :)