Global State Management Options in Angular

Sean Haddock
Stackademic
Published in
7 min readApr 26, 2024

--

Photo by Kyle Glenn on Unsplash

In this post I want to run through the various state management options we now have available in Angular.

What is State?

First, what is state? State is any piece of information about the application at a given point in time.

Shared (or Global) state, is state that is shared among multiple components.

An important thing to keep in mind is that not all state needs to be shared! But if a piece of state needs to be shared beyond a parent and child component, you will need an option for global state management.

Requirements for State Management

Requirements for State Management will vary by application, but I’m going to use the following when evaluating the different state management options:

Requirements for State Management

Of these requirements, the first is the most important and it’s a deal breaker if it is not met.

Options for State Management in Angular

In Angular we have the following options for state management:

1.Every component loads the state it needs from the API

2.Inputs and Outputs

3.Variables in Services

4.Observables in Services

5.NgRx

6.Signals in Services

7.NgRx Signal Store

Let’s quickly review how each of these options fulfills our requirements.

Every component loads the state it needs from the API

Every component loads the state it needs from API

This method would likely be the first one tried by new developers. It’s easy at first but becomes complicated as the application grows.

In addition, it fails to satisfy nearly all of our requirements, including the first one which is a deal breaker.

Inputs and Outputs

Inputs and Outputs

The next option, Inputs and Outputs, fairs a little better but still fails to meet half of our requirements.

Again, it fails to meet our first requirement because although we can theoretically achieve consistent state that updates multiple components and routes without re-retrieving data, this approach is anything but easy to follow.

Passing data far down a component tree, and daisy-chaining the events back up becomes very difficult to follow and maintain, which is the biggest knock against this approach.

Variables in Services

Variables in Services

This option is a huge leap forward over the previous two. However, it still only meets half of our requirements.

Notable failures are that it doesn’t work with OnPush Change Detection (without extra work) and it can be difficult to run business logic when state changes. Sometimes you need to introduce a component input to pass in the service variable so that you can do something in ngOnChanges.

Observables in Services

Observables in Services

With this option, we are now entering the realm of acceptable solutions. It only fails on two requirements.

I think it’s pretty non-controversial to say that learning how to work with observables is difficult, especially for those new to Angular or JavaScript in general. That doesn’t mean you shouldn’t use it of course. It’s just that, all things being equal, something that is easy to use would be preferable to something that is more difficult.

Also, I think having the service be responsible for both performing CRUD operations against the API and state management does not adhere as closely to the Single-Responsibility principal as it could.

NgRx

NgRx

I think using NgRx is a step above implementing your own Subject-in-a-Service pattern because it makes it easier to maintain consistency across the code base and it better adheres to the Single-Responsibility principal.

But it’s not easy. Again, I’m not suggesting that we only do things that are easy. Sometimes difficult problems require difficult solutions. But if there’s an easier path that’s just as good, why not take it?

Signals in Services

Signals in Services

I think using signals in services is a good alternative to observables in services because they are easier to use and adequate for most circumstances.

The only knock against this approach is, again, that I don’t think it adheres to the Single-Responsibility principle as well as it could, since the services are responsible both for CRUD operations and managing state.

NgRx Signal Store

NgRx Signal Store

Finally, I think the Signal Store may be the best option going forward. I think it is the only approach that checks all of our requirement boxes, although I could see someone making the argument that the previous three approaches do as well. But as I’ve said, I think the previous three approaches fall just slightly short.

Other Advantages with NgRx Signal Store

In addition, I think there are other advantages to using the Signal Store.

The first is, why reinvent the wheel? Of course, you could implement the same functionality on your own, but I just feel like if there is already a tool dedicated to that functionality, and it’s easy to use, why not use it? Also, I think it makes it harder to deviate from a common pattern which is helpful in large teams with developers of mixed experience.

A second advantage I see is that it is championed by leaders in the Angular community. I am a big believer in leaning on the experience of the community. I know there are developers who have implemented many applications of many different types, so I always like to take advantage of lessons they’ve learned.

A third advantage is the use of Redux DevTools that allow you to see the state change in the application. Currently this is offered through the ngrx-toolkit.

Finally, I think the Signal Store offers some helpful features. The first I’ll mention is rxMethod, which makes it trivial to prevent multiple HTTP calls when loading state into the store:

rxMethod

Here we can easily add a debounceTime() operator, without having to create an additional observable just so that we can debounce the load call. Again, it’s not that you couldn’t create your own observable to do this, but I think being able to use it in this way is cleaner and easier.

The second set of features I’ll mention is DeepSignals and patchState. I think the benefit of these is best illustrated by a comparison with the signals in a service approach.

With the Signal Store, you can update state individually or as a whole but still always read each slice as individual signals:

Updating state with Signal Store

With signals in a service, to mimic the behavior of the effect() above, you either have to create a signal for each piece of state individually, which means you have to update them individually:

Updating state individually with Signals in a Service

Or you can manually make each property of a complex object a signal, which is more verbose:

Complex object in state with Signals in a Service

With the Signal Store, UserState is a DeepSignal which means that each property is also a signal, so that you don’t have to declare and set each signal property in the verbose way above.

So, these are a couple of helpful features we already get with the Signal Store, and I’d be willing to bet there will be more in the future.

Option Ranking

Given all I’ve said above, this is how I would rank the options:

Option Ranking

Again, starting with the Observables in Services approach we enter the realm of acceptable solutions. From there, we only achieve incremental gains, but I think gains, nonetheless.

Even if a solution is only slightly better why not use it if it’s not any more difficult and does not introduce additional complexity? I would place the NgRx Signal Store in this category.

Bibliography

Stackademic 🎓

Thank you for reading until the end. Before you go:

--

--