Comparing State Management Libraries for Progressive Reactivity

After implementing my colors app across every major state management library, I can now evaluate which one best supports the "progressive reactivity" pattern we've been exploring. The assessment relies on three key metrics:

My implementations deviate somewhat from typical usage patterns for each library. NGXS, for example, supports numerous imperative approaches that I deliberately avoided. I also built custom utilities to push each library toward maximum reactivity. The specifics will be covered in the upcoming final three articles of this series.

Let's examine what the comparisons reveal.

Imperative Code Results

The table below shows the smallest number of imperative statements achievable for each library when handling the most complex scenario in my colors app:

Library Imperative Statement Count Imperative Statement Count with Reactive Utils
NGXS 11 7
NgRx/Store 7 7
Akita 10 4
Elf 10 4
NgRx/Component Store 10 4
Subjects in a Service 10 4
RxAngular 5 4
StateAdapt 4 4

Angular alone requires a minimum of 4 imperative statements. StateAdapt achieves the highest possible reactivity, which makes sense given it was designed specifically for this pattern.

RxAngular follows closely with 5 imperative statements, unsurprising since Michael "Rx" Hladky created it.

Standard NgRx/store impresses with just 7 imperative statements. What explains this? And why do the remaining libraries lag behind?

NgRx/Store traces its lineage through Redux back to Flux. The connection to reactivity and declarative programming becomes clear when looking at the original Flux presentation from 8 years ago:

"We have an external handler that goes in and changes some of their state. Each have their own state, but the [event] handler goes in and modifies them. All of the logic to modify state based on a new [event] is in the handler itself. And so those 3 pieces no longer have the ability to maintain internal consistency. They basically don't have that information. They've already ceded that control to the handler. What we want to do instead is internalize this control. We want to move all the control into the individual pieces, so that the state is right next to the logic that updates that state."

That description is essentially declarative programming, as outlined in Rule 1. The creators called it "unidirectional data flow" and "single source of truth," but those terms simply describe reactivity and declarativeness. This entire ecosystem of state management libraries emerged from the pursuit of writing declarative asynchronous code.

Criticism of Flux, Redux, and NgRx fell into two categories: objections to the heavy boilerplate (actions, dispatchers, reducers, thunks, etc.) and skepticism toward reactivity itself.

Since RxJS hadn't gained traction yet, Flux can't be faulted for lacking comparable syntax. But the pushback on verbosity was justified — building everything takes longer, business logic becomes obscured, and refactoring feels painfully slow.

Resistance to reactive thinking typically stems from inexperience. Whether someone has coded for one year or ten, witnessing how out-of-context imperative updates create exponential complexity is what builds appreciation for the clarity that declarative code provides.

Lines of Code Results

The following chart displays total lines of code for each library across all complexity levels in my colors app:

Lines of Code for 8 State Management Patterns in Angular

After building reactive utilities for each solution, I managed to trim 10-20% of the code across the board:

Lines of Code for 8 State Management Patterns in Angular with Reactive Utilities

StateAdapt needed no changes, since it was already engineered for minimalism and reactivity.

The Subjects in a Service (RxJS) pattern still exists, hiding beneath NgRx/Component-Store. With the reactive utilities, the store and component class code turns out to be identical for those patterns.

A notable observation: StateAdapt requires less than half the code of NGXS. If you've already built Level 3 complexity in NGXS, accommodating Level 4 (reused state logic) demands so much additional code that starting over entirely with StateAdapt would actually be less work. This extreme scenario works well for illustrating pattern differences, though most real projects won't have such minimal business logic.

Popularity Results

Library Weekly Downloads GitHub Stars
NgRx/Store 486k 3.9k
NGXS 118k 3.3k
Subjects in a Service N/A N/A
NgRx/Component Store 80k ?
Akita 42k 3.5k
RxAngular 37k 1.3k
Elf 7.8k 1k
StateAdapt 139 25

StateAdapt is still early-stage and shouldn't be used in production environments. The 1.0 release should arrive within a couple of months, and contributions via stars are welcome at StateAdapt.

Which Library Should You Choose?

Every option has significant drawbacks!

I built StateAdapt as the ideal progressive reactivity solution, but it's not production-ready. The article series guided the syntax design, so I deliberately postponed the production release until after publishing.

NgRx/Store offers the safest bet, yet its second-highest line count reveals a non-progressive syntax. Start with a simpler solution and when business needs shift toward complexity, you're stuck: either postpone the inevitable mess or bite the bullet and migrate. Anything you build outside NgRx/Store becomes a dead end once you need to switch, given the extensive rewriting required.

NGXS follows a similar pattern but with even more code. It also embraces many imperative styles, so teams wanting progressive reactivity should thoroughly understand Rule 1 before committing.

For NgRx/Store and NGXS, consider them only for highly complex applications where you'll apply them consistently, especially to features that might someday benefit from selectors and Redux Devtools. For features certain to stay simple, the other four libraries could serve you well. But predicting 100% that a feature won't grow is rarely realistic.

Subjects in a Service, NgRx/Component-Store, Akita, Elf, and RxAngular lack selector support. Redux Devtools integration is also limited in Akita and Elf, showing only action types without payload details. Their popularity scores trail the others (except StateAdapt), and notably, both Akita and Elf share the same creator — while Akita maintains support, he now favors Elf for various reasons.

Honestly, I don't have a clear-cut answer. This wasn't the conclusion I expected when I began exploring each library. Every single pattern faces major challenges, including StateAdapt's pre-1.0 status and limited adoption.

Since I want to assist Angular developers regardless of their chosen library, my upcoming articles will demonstrate how I made each pattern more minimal and reactive. Ideally these improvements get adopted upstream, though that depends on community reception.

My Recommendation

Since some guidance is expected, here's what I suggest:

  • Choose NgRx/Store with the reactive utilities from my next article for any feature showing even a hint of growing complexity.
  • Prefer Angular's built-in features and RxAngular elsewhere, given RxAngular's solid popularity, strong reactivity, and abundant utilities.
  • Experiment with StateAdapt in side projects to accelerate its path to 1.0, finally giving us a state management solution with 100% reactivity, selectors, and Devtools support without requiring a massive initial investment. Report any bugs you encounter.

Alternatively, if you're already invested in a library, apply the reactive utilities from my upcoming articles. Your syntax then approximates the other options, making lateral moves easier if you decide to switch. For example, adopting these utilities in Akita produces code closely resembling other class-based solutions — and closer to StateAdapt too, simplifying a future transition when it stabilizes.

Miscalculations about complexity (which WILL occur) leave you with difficult trade-offs: excessive boilerplate when you overestimate, or tangled RxJS that was never designed for complex synchronous state when you underestimate.

Final Thoughts

Ultimately, your judgment of progressive reactivity's importance determines the right choice. I've made my case that it yields cleaner code and avoids dead ends. Regardless of your decision, I hope the framework presented here helps you evaluate options for your specific needs. Consider the findings above, share your perspective, and stay tuned for detailed breakdowns of each library in the next three articles, complete with StackBlitz examples.