This is a brief overview of state management in Angular (or any front end application). It’s easy to get distracted by the intricacies that come along with front end development. There are so many little things that have to be done in front end development. We deal with pixels, percentages, view ports, accessibility, design, and the list goes on. So here’s a quick refresher on state management, a critical part of most applications.
What’s the problem?
Maintaining state in medium to large sized Angular applications can be a challenge. We load data from various API’s, after that we will typically:
- Map model properties to some more component/angular friendly properties.
- Spread the data out and add or extract what you need.
- Toggle other API calls.
From there, we figure out where to store the data. We can make an API call and simply store the data locally to some component. The problem with that? Chances are the component is not permanent. If it’s destroyed and re-initialized, you just have to go out and get that data from the API again.
A few ways to approach that state problem:
- Store the data in a higher level component where you’re “certain” the component will not be destroyed.
- Create a service (singleton for which ever module(s) you need it in). Store your data here so any component that has access to the service can access.
- Use some sort of state management library like NgRx, NgXs, etc…
All of these approaches are often used in conjunction. Store data in a component, and maybe let caching maintain the state. However, caching is often not an acceptable solution. If your data is likely to change, you can’t necessarily rely on cached data.
If you just use a service, you run the risk of losing track of how and when your data is being changed. Any component can access it, and as the application grows keeping checks and balances on your data becomes increasingly more difficult.
The idea of maintaining the state of your data is simple, but the simplicity quickly fades away as more developers are involved and the size of your application grows. People will make mistakes and mutate data that shouldn’t be mutated. Or make API calls that do not need to be duplicated.
As that is happening we have to think about how to handle intermediate states. The times we are waiting for data to come down the pipeline and the times when the pipeline breaks. Loading, errors, success, idle time. Those are what I call transient states, and we need a way to consistently handle those states at lowest and highest levels of the application.
Is there a solution?
I like to think so! If we look at all of the tools in our tool belt, we can come up with some creative solutions. For smaller applications, I recommend sticking with singletons. Where each service maps to whichever API it is pulling data from. In addition, that service can maintain flags for loading, errors, idle, and so on… If your data can afford it, then adding client side caching can increase the performance of your application dramatically. Each one of your services can have its own map where ever you are storing your cached data (local storage, locally scoped value, etc…). That map should handle storing the data, the max-age of your cached data, and a flag that will force the cached data to be ignored at the next request.
If you’ve got a larger application, some guidelines should be set for the application. The one-way data flow concept has a lot of benefits. It makes your data more predictable and makes it more difficult for interference from other components. The easiest way I’ve accomplished a protected one-way data flow is with NgRx. Dispatch an action, the effect of that action will make an API call, and then that data will be inserted into a store (basically just a dumb singleton). You then listen for that data reactively by selecting data from the store and then observing it.
Both of those solutions have issues. As stated before, just depending on services to maintain your data will more than likely result in inconsistent patterns and thus, mistakes. NgRx requires a lot of boiler plate code, and has a fairly steep learning curve if reactive programming is not something you are well practiced in.
In the end, if your application size dictates it, having strict patterns that NgRx enforces will benefit the maintainability and growth of the application. It creates a baseline of how data is managed and forces developers to follow patterns that are otherwise very difficult to follow. That doesn’t mean it has to be NgRx. There just has to be a pattern to it.
This is just part 1, I’ll be expanding on all of this soon so we can look into some common state management patterns a long with some tricks and tips.
I am most looking forward to explaining my approach on the transient states I mentioned above using NgRx.