What is state management and does our application need it?
When we start creating a new application, we should adhere to the basic postulates: that the code we write is simple, easy to test, that it can be maintained, and that it is readable and understandable for the colleagues we work with or to those will subsequently take over maintaining our application. Furthermore, at the very beginning, we should think about how complex the structure of our application is and how we will manage the data we have without our application being fast, user friendly, or reliable.
When working on developing a more complex Angular application, data management can be very demanding. That’s why at the beginning, it is important to separate whether the application we are working on has a complex architecture and processes a large amount of data – such as any editor or any real-time display of data, e.g., results of sports matches, etc. – in which case, it would be wise to consider using one of the solutions for state management, i.e., to find a solution that will help us manage the data we have.
Practically speaking, the state of an application is the entire memory of the application. It is composed of data received by API calls, user inputs and data from other sources. State management is a concept that practically separates your state from the logic and user interface and ensures that there are no more copies of your data. REDUX, which emphasizes reactive programming, is one of the most popular state management patterns. Using REDUX there is a so-called single source of truth, the application observes that data. When a change occurs, your application responds to that change as required by a particular component. This way, you save time for data synchronization, and you have an application that is consistent and has fewer flaws. A specific example is that it is like a list of recipes, a list of users in an application, or something similar.
Angular applications consist of several components and each of these components has its own state without having awareness of the state of the other components. For information sharing between parent-child components, @Input and @Output decorators are used. This approach is perfectly fine if we have a small number of components like in the example below.
In addition to this, if you have a situation where multiple components use the same set of data, create your own service to exchange that data. Overhead is small and the implementation is fairly simple.
However, as the application grows, there will be a lot of components and a lot of modules. This is where the situation becomes much more complex, prone to errors, and managing the state becomes quite difficult. In such cases where there is a complex application architecture, NGXS, NGRX or Akita are the preferred solutions to make managing the state easier.
In the example below, if a change has occurred in Component 3, it first forwards the data to Component 1 and then all the way to Component 5, which monitors the changes.
No matter whether you choose NGXS, NGRX or Akita as the state management solution, it will transform your situation from the previous chart into something like in the following one, so now the components will communicate directly with the Store, where the data is stored. That is, in all applications where state management should be dealt with, we use state library management as a tool that provides us with mechanisms on how to retrieve data from different places, combine them, and modify that data.
Which state management should you choose?
Now that we have established that we need one of the state management solutions, the question is which one to choose and where to start.
Both NGXS and NGRX are state management libraries that can be used by Angular applications. NGXS has certain advantages when we are developing an Angular application and I’d like to point them out to you.
- “More is less”: NGXS strives for simplicity, it aims to achieve as much as possible with as little code as possible and to minimize the so-called “template code” (boilerplate code). Of course, NGRX also has a plugin for boilerplate code (https://github.com/johnpapa/angular-ngrx-data) but I still believe NGXS is the better solution because it offers this plugin out-of-the-box. NGXS is built on the CQRS pattern (Command and Query Responsibility Segregation), which is also used in libraries such as Redux and NGRX, but it reduces the boilerplate code by using modern TypeScript functions such as decorators and classes. Specifically, the state.ts file in NGXS replaces three files in NGRX (reducer.ts, effect.ts, selector.ts). This definition may give off the wrong impression to many who have already used NGRX, so that they think that the code from all three files was just transferred to one, but this would not be good or clear. Furthermore, the creation of the action itself and the code contained in the component is minimized.
- DI (Dependency Injection): One of the basic characteristics of Angular, and NGXS allows us to use it. In addition, since NGXS was developed by a group of Angular developers, the Angular approach is very present and can be observed in that it uses classes, decorators, TypeScript feature and the like.
- Promise: In addition to the fact that the action method can return an Observable, it can also return a Promise.
- Actions: Asynchronous in NGXS, allowing them to have a life cycle. That way we have the option of doing something else after one action or a collection of actions is completed. In NGXS, this is done quite simply.
What is NGXS and how does it work?
As I have already mentioned, NGXS is a state management library which is very similar to NGRX, with the difference that it has less boilerplate code and is easier to learn.
There are 4 basic concepts in NGXS that you should understand before you integrate it into your project.
- Store: The key element in the entire state management process Is the store, which facilitates the interaction between the components and the state. A reference to the store can be obtained via Angular dependency injection, and then later used for dispatching actions to the store via the store.dispatch() method, which will, in turn, trigger the state modifier functions, or for retrieving the application state via Selects.
- Actions: An action is an instruction dispatched to the store. Each action contains a type field that is its unique identifier and optionally some metadata (payload) that any action handler can read.
- State: In the context of NGXS, States are classes that define a state container. These classes hold different portions of the overall application state. Suppose your application consists of three feature modules, X, Y and Z. Each of these modules handles different parts of the overall state. X information will always be maintained in the X section in the state.
- Selects: Selectors are functions that slice a specific portion of State from the global state container. In NGXS either the select method on the Store service can be called or the @Select decorator can be used. The same selects are often used in several different places, however there can be complex selects kept separate from the component. NGXS has an @Selector decorator for that purpose. This decorator will memorize the function for performance, as well as automatically snip the part of the state being dealt with.
If you are developing an enterprise Angular application that has a lot of data to work with, my advice is to use one of these state management options. I’m not saying NGRX or Akita are bad solutions – they are good solutions. In my opinion, NGXS is simpler, made for Angular applications and a good choice for people who have not already had experience with other solutions because it:
- Simplifies state or data management
- Uses decorators, classes, and DI
- Provides an "Applications Intelligence to live" location
For more information on NGXS itself you can check out the official documentation and watch YouTube videos made by Mark Whitfeld, Team Lead in the core team for NGXS library development.
Furthermore, there are many projects available on GitHub, one of which was developed by Mark Whitfeld himself. The examples above are from this project; you can see in this example how useful selectors can be and how to use them smartly.