MiniRx "Feature Stores" deliver straightforward yet robust state management.
What sets MiniRx Feature Store apart from @ngrx/component-store and @datorama/akita? Let's find out over 10 rounds in the fighting ring!

Disclosure: I maintain MiniRx Store and will do my best to stay objective, though it isn't always easy.
To be fair: both Component Store and Akita are excellent state management tools. This will be a tough match, but I'll ensure everyone walks away unharmed!

Introducing MiniRx

MiniRx is a complete Redux store built on RxJS, offering actions, reducers, meta reducers, memoized selectors, effects, and Redux DevTools integration.

While the Redux pattern excels at managing large-scale state, it comes with boilerplate overhead—actions, reducers, and dispatches—which can feel excessive for simple application features.

That's where MiniRx's Feature Store steps in: it provides a more streamlined state management approach, letting you skip Redux ceremony and work directly with a dedicated feature state through the FeatureStore API:

  • setState() modify the feature state
  • select() expose feature state slices as RxJS Observables
  • effect() handle side effects like API calls and update feature state
  • undo() revert setState operations with the UndoExtension enabled
  • get state() retrieve the feature state imperatively

MiniRx adapts to your state management needs:

  • Use the Redux Store API for complex scenarios
  • Use the FeatureStore API for simplicity

Generally, you'll start with FeatureStore and switch to Redux's Store for advanced features.

How Feature Store operates

Feature Store relies on Redux internally:
It automatically creates a feature reducer and a corresponding setState action.
This reducer integrates into the Redux Store, and the feature state becomes part of the overall global state.
When you invoke setState(), the Feature Store dispatches its setState action with the new state as payload, and the feature reducer updates the feature state accordingly.

MiniRx Feature Store vs. NgRx Component Store vs. Akita — figure 1

You can review the FeatureStore code here.

Resources

Comparing MiniRx Feature Store, NgRx Component Store, and Akita

Let's bring MiniRx Feature Store into the spotlight by pitting it against two other widely-used state management solutions: @ngrx/component-store and @datorama/akita.

The contestants

NgRx Component Store (13.0.1)

Component Store focuses on managing local/component state and serves as a substitute for the "Service with a Subject" pattern.
It leverages RxJS/ReplaySubject internally (check this reference). Services extending ComponentStore expose state as RxJS Observables via select, while setState and patchState handle updates.

Documentation: https://ngrx.io/guide/component-store

Akita (6.2.4)

Akita positions itself as a "state management pattern," offering specialized classes like Store, Query, EntityStore, among others.
Its Store utilizes RxJS/BehaviorSubject (see this source).
With Akita's classes, you construct a reactive state service that surfaces state as RxJS Observables (via select on a Query instance), and updates go through the update method on Store.

Documentation: https://datorama.github.io/akita/

MiniRx Feature Store (3.0.0)

MiniRx is a "hybrid" store, combining Redux with RxJS/BehaviorSubject (see this reference). It offers the robust Redux Store API, closely resembling @ngrx/store and @ngrx/effects.
Simultaneously, the FeatureStore API lets you sidestep typical Redux boilerplate.
Extend FeatureStore to create a reactive state service, with select returning RxJS Observables for state changes and setState for modifications.

Documentation: https://mini-rx.io/docs/fs-quick-start

They all seem quite alike, don't they? But the real differences are yet to surface—time to get the fighting ring ready! :)

Three State Management Libraries Enter the Ring

Ten rounds of comparison are ahead of us.

Round 1: Initial Configuration

What does it take to get a reactive state service up and running?

All three libraries follow the same pattern: you define a state interface and an initial state object.

One caveat: your state has to be an object. You cannot directly manage a primitive like a number or string as a standalone unit.

interface CounterState {
    count: number;
}

const initialState: CounterState = {
    count: 42
}
Enter fullscreen mode Exit fullscreen mode

MiniRx Feature Store

Your state service inherits from FeatureStore:

@Injectable({providedIn: 'root'})
export class CounterStateService extends FeatureStore<CounterState> {

    count$: Observable<number> = this.select(state => state.count);

    constructor() {
        super('counter', initialState)
    }

    increment() {
        this.setState(state => ({count: state.count + 1}))
    }

    decrement() {
        this.setState(state => ({count: state.count - 1}))
    }
}
Enter fullscreen mode Exit fullscreen mode

With MiniRx, you supply the initial state plus a feature key — in this case, the string "counter". That key determines where the counter state gets registered inside the global state tree.

Component Store

For Component Store, you extend ComponentStore and pass in the initial state:

@Injectable({providedIn: 'root'})
export class CounterStateService extends ComponentStore<CounterState> {

    count$: Observable<number> = this.select(state => state.count);

    constructor() {
        super(initialState)
    }

    increment() {
        this.setState(state => ({count: state.count + 1}))
    }

    decrement() {
        this.setState(state => ({count: state.count - 1}))
    }
}
Enter fullscreen mode Exit fullscreen mode

The setup mirrors MiniRx closely, except there is no feature key to worry about. Every ComponentStore instance stands on its own, and providing the initial state is optional — the docs cover lazy initialization.

Akita

Akita takes a different approach: you create two services. One extends Store, the other extends Query:

@Injectable({providedIn: 'root'})
@StoreConfig({ name: 'counter' })
export class CounterStateService extends Store<CounterState> {
    constructor() {
        super(initialState)
    }

    increment() {
        this.update(state => ({count: state.count + 1}))
    }

    decrement() {
        this.update(state => ({count: state.count - 1}))
    }
}

@Injectable({providedIn: 'root'})
export class CounterQuery extends Query<CounterState> {
    count$: Observable<number> = this.select(state => state.count);

    constructor(store: CounterStateService) {
        super(store);
    }
}
Enter fullscreen mode Exit fullscreen mode

Akita demands the most boilerplate. Extending Store is familiar enough, but the feature key arrives via the @StoreConfig decorator, and you must also extend Query, handing it your Store instance.

Components therefore need access to both services — one for reads, one for writes.

Round 2: Bundle Size

Looking at just the basic setup, let's measure the bundles with source-map-explorer.

MiniRx Feature Store

combined: 152.39 KB

Component Store

combined: 152.25 KB

Akita

combined: 151.61 KB

The gap is narrow: Akita comes in lightest, and MiniRx costs just under 1 KB more.

That extra weight has a reason. MiniRx ships with the complete Redux machinery already on board, so paying for the Redux API later adds almost nothing to your final bundle.

Round 2.1: Adding Redux to the Mix

MiniRx Feature Store + Store API (Store + Effects) via Angular Integration (mini-rx-store-ng)

combined: 156.9 KB

NgRx Component Store + NgRx Store

combined: 164.17 KB

NgRx Component Store + NgRx Store + NgRx Effects

combined: 171.45 KB

If you want to verify these numbers, the repository at https://github.com/spierala/mini-rx-comparison contains every setup ready for source-map-explorer.

Round 3: Local State vs. Global State

Where does each solution fit in the spectrum between component-scoped and application-wide state? And how long does its state live?

MiniRx Feature Store

Underneath, MiniRx is a Redux Store with one global state object — a single source of truth. Each Feature Store registers its slice into that global object.

The design clearly favors global state that lives as long as the application runs, but Feature Stores can also be destroyed. When you tear one down, its slice is removed from the global state, which makes Feature Stores viable for component-level state too.

The MiniRx Angular demo shows this pattern in practice.

Component Store

Component Store instances operate in isolation, with no connection to any global store like @ngrx/store.

Their lifespan is flexible: bind one to a component for local state, or keep it around for the whole application.

Akita

Akita Stores exist side by side without a shared global state. They are destroyable too, so you can follow the Akita docs guide to manage local component state with them.

Round 4: Redux DevTools

MiniRx Feature Store

The Redux DevTools Extension is built into MiniRx.

Since Feature Store state joins the global state object, you can inspect everything directly in Redux DevTools.

Component Store

There is no official way to hook Component Store up to Redux DevTools.

Akita

Akita offers a plugin for Redux DevTools.

The individual store states are combined into one big object so DevTools can display them all. See the implementation in the Akita DevTools source.

Round 5: Selecting Across Stores

How do you reach into another store from your current state service?

MiniRx Feature Store

Because every Feature Store contributes to the global state object, you can pull any feature slice out of the Redux Store instance at any time with store.select.

Alternatively, RxJS combinators like combineLatest or withLatestFrom let you merge the state of other Feature Stores with your own state observables.

Component Store

The select method of Component Store accepts additional observables as dependencies, as described in the docs.

Those observables can point at other services, enabling the state of one ComponentStore instance to feed directly into another.

Akita

Akita supplies combineQueries to merge state from multiple Query instances — a thin wrapper over RxJS combineLatest.

You can read the combineQueries source to see for yourself.

MiniRx Feature Store vs. NgRx Component Store vs. Akita — figure 2

Round 6: Memoized Selectors

Memoization speeds up selection by caching derived state, and the selector API (createSelector) is also a composition tool — small selectors snap together into bigger ones.

Two well-known examples:

MiniRx Feature Store

MiniRx ships with memoized selectors enabled from the start.

The same createFeatureSelector and createSelector functions work for both the Redux Store API and the FeatureStore API.

Details live in the Feature Store memoized selectors documentation.

For a concrete example, see the memoized selectors at work in the Todos State Service from the MiniRx Angular Demo.

Component Store

There is no official memoized selector solution for Component Store.

You could pull in @ngrx/store just for its selectors, but adding a full Redux store feels like overkill. Redux Reselect might fit the bill more appropriately.

Akita

Akita offers no memoized selectors out of the box, though adding Redux Reselect is likely an easy fix.

Round 7: Effects

Effects handle side effects like API calls, and they give RxJS flattening operators (switchMap, mergeMap, etc.) a natural home for taming race conditions.

MiniRx Feature Store

MiniRx Feature Store supports Effects — see https://mini-rx.io/docs/effects-for-feature-store.

The equivalent tooling exists in the MiniRx Redux API as well: https://mini-rx.io/docs/effects

Component Store

Yes, Effects are available: https://ngrx.io/guide/component-store/effect

Akita

Effects are also supported: https://datorama.github.io/akita/docs/angular/effects.

They are distributed in a separate package (@datorama/akita-ng-effects) and, notably, are not tied to any specific Store instance.

Round 8: Undo

Rolling back state changes is a common need — how does each library handle it?

MiniRx Feature Store

The UndoExtension brings undo capability to MiniRx.

This is particularly useful after an optimistic update goes sour, say when an API call fails. Both the FeatureStore and the Redux Store API can revert specific changes, and Feature Store exposes a dedicated undo method.

More details: Undo a setState Action

Component Store

No undo support here.

Akita

The State History Plugin (https://datorama.github.io/akita/docs/plugins/state-history/) is Akita's answer to undo.

Its API is far more extensive than Feature Store's, but zeroing in on one exact state change — essential for reversing optimistic updates — looks trickier to achieve.

Round 9: Immutable State

Immutability is the safety net of state management. Changes should happen only through deliberate API calls like setState, update, or dispatching an Action in Redux.

Accidental mutations lead to unpredictable behavior and bugs, and immutable state is the shield against them.

MiniRx Feature Store

MiniRx provides the Immutable State Extension to enforce this discipline.

Once the ImmutableStateExtension is active, both the Redux Store API and the FeatureStore API operate on immutable data. The extension "deepfreezes" the global state after every update; any attempt to mutate throws an exception.

Component Store

There is no immutability enforcement built into Component Store.

Akita

Akita "deepfreezes" the state object on every update, but only in DEV mode, as seen at https://github.com/datorama/akita/blob/v6.2.0/libs/akita/src/lib/store.ts#L181

Round 10: Framework Independence

MiniRx Feature Store

MiniRx does not care about your framework. It works with any framework, or none at all.

Check out this Svelte demo for proof: https://github.com/spierala/mini-rx-svelte-demo

Component Store

Component Store is Angular-only. Angular appears as a peer dependency in its package.json.

Akita

Akita is framework-agnostic too. This article on Svelte and Akita demonstrates how far it stretches beyond Angular.

MiniRx Feature Store vs. NgRx Component Store vs. Akita — figure 3

You made it through all ten rounds. I hope you enjoyed the match!

Final Thoughts

Every library held its ground — none of them faltered.

Which one earned your vote?

Consider showing support with a star on GitHub:

Additional Context

Topics Left Out

A few capabilities didn't make it into this comparison, but they're worth mentioning for the sake of completeness:

  • Akita: EntityStore, Transactions, Akita Immer, Persist State, CLI
  • Component Store: updater method, tapResponse operator

@rx-angular/state

There's another interesting library that follows a similar path as NgRx Component Store:
https://github.com/rx-angular/rx-angular/blob/master/libs/state/README.md

Perhaps we'll cross paths in a future showdown!

Acknowledgments

Reviewers for this Post

Works That Shaped This Series

Image Credits