State management or Observable services ? All right, all right. We got it. Everybody got their flavor. And that’s ok.
But what are you going to do when all those flavors try to mix up in a team of developers with strong opinions about state management?
Some will say that using a state management library is way too complex, others will say that’s the way to go, using all the tools in the arsenal and using observable services is just not enough. As the old saying goes, the truth is somewhere in the middle.
What I want today is to explore together with you two of the most popular ways that you can do state management in Angular.
What is state management anyway?
In the frontend world, state management refers to how you organize and maintain data that reflects the current state of the application. This can mean user authentication, a list of groceries, or toggles that open or close. Anything relevant to user experience can fit here.
Using a form of state management comes with some benefits:
- Easy to maintain data
- Simplified debugging process
- Improved performance
- Consistency
All the above benefits come because using state management centralizes the data in a well-defined manner and has uniform access and ways of propagating data changes.
That comes in handy when you’re building applications that need to scale. When you scale up, it’s hard to keep track of all the changes so without a process, you are doomed to fail.
Let’s take a look over two forms of state management in Angular and have a comparison between them.
Observable services
Probably one of the easiest-to-grasp method to implement state management are Observable services.
Observable services use RxJS Observables behind the scenes to manage the state reactively and asynchronously.
Using observables for me is already a great plus as they act as data streams.
A simple example would be like this:
Once you create a service, you can change the data by using the methods that you create (in our case `setData()`) and access data through the observable data$.
Using it in a component would be like this:
Or, a better approach, would be to use the async pipe inside the template.
<div>{{ dataService.data$ | async }}</div>
This way, you will not have to worry about unsubscribing because the async pipe will do it for you.
Depending on the method you’re using for injecting the service in the components, you can use it as a singleton service. This way, you ensure that you have only one service and only one source of truth for your data.
Every time you update the data through the methods of the service, all of the places that are subscribed to data$ will be notified and change their value.
It’s simple, it does the trick. But does it scale?
It depends on how your team is using it. If the application is small to medium, probably you won’t see the difference because the logic wouldn’t be that big.
The problem that I’ve seen in practice is that people will move the logic of the component under the service. And when I say move it, I mean like shoving the dirt under the rug.
The service will become big, and very hard to maintain. And what are you going to do with the side effects? You can either put it in the component or the service. Too many dependencies for my taste.
The worst thing you can do is to split the logic in two. You either add the side effect in the component or the service. No matter which one you will do, keep it consistent.
But what if I tell you there’s a way to enforce this kind of consistency?
State management – NgRx
In complex applications, you need processes to keep the code consistent. Some libraries around state management try to solve this issue by adding more layers of abstraction. Their objective is to enforce a way of manipulating data and improving extensibility.
NgRx is the subject today.
A heavy Redux-inspired library, NgRx is a library that manages state, taking into account many of the OOTB features that Angular provides.
In Observable Services, the relationship is simple. The component subscribes to the observable in the service and that’s it.
In NgRx, there is a whole process with its terminology and you have the following:
- Actions
Plain objects that represent events. They are used to initiate state change
- Reducers
Action handlers and update the application state
- Selectors
Extract specific pieces of data from the state. Can also be used to combine data
- Effects
Side effects that are triggered by actions. Good for API calls or asynchronous operations
- Store
Central state management object. It handles dispatching actions, retrieving state and subscribing to state change
A lot, to be honest. The first time I saw this it felt overwhelming because I didn’t understand what problem it was trying to solve.
The problem that I had was just displaying a list of cars. Why wasn’t it enough to just call the service and that’s it?
One thing that I like is having a uniform way of changing the state. You do that by emitting actions. This way, you can easily reproduce the state and follow the series of events for debugging purposes.
Modern problems? Modern solutions
I had several problems that I found some answers to:
- Why so many concepts?
The concepts come from the separation of concern. I appreciate how I separate how I update the store from the side effects for example. Through side effects, you keep out all of your dependencies from the store.
Imagine you would need to add a notification message after you update a car. Probably a good place to do that is in the side-effect. This way, each time you update the car, no matter the place, you will still have the same user experience - Too much boilerplate?
Yes, I do agree with that. This is one of the biggest trade-offs that comes with this declarative way of writing code. There is a solution to it. You can use @ngrx/schematics to make your life easier when it comes to generating the code for your store - Everything is asynchronous and scattered all around the place, how do I debug this?
This is a problem that you can find using Observable Pattern as well. Solving that problem is not as easy as in NgRX
Once you get the hang of it, it gets easier. Understand the Redux pattern and you’ll find it easier. Combine that with the right tools for the job. I recommend the Redux DevTool extension for your browser.
This way, you can listen to the events and state changes in time and do time travel debugging.
And what do I choose?
State management or Observable services? It depends on your needs. If you have a simple application that doesn’t require too much state management and logic, stick to Observable Pattern. It will solve most of your problems and you also keep it simple.
If your app is complex, the state is intertwined and you need to enforce a process, go with NgRx. It’s going to give you some complexity to your application but sticking to your process will allow you to avoid the pains of scaling.
Let’s wrap this up with a comparison table.
Using NgRX can be a great tool to add to your knowledge arsenal. Not only will you probably encounter it in big applications, but also you will learn about Redux, a pattern that is used not only in Angular but in other frameworks as well. Transferable knowledge always comes in handy.