From RxJS to Signals: The Motivation Behind MiniRx Signal Store
Angular 16 shipped with a brand-new reactive primitive: Signal.
The arrival of Angular Signals is arguably one of the most significant shifts the framework has seen in years:
- Is RxJS obsolete now?
- Should Signals replace RxJS across the board?
- Can Signals and RxJS Observables work together?
- What are the new Angular best practices?
Driven by these questions, MiniRx began experimenting with Signals to explore what modern Angular state management should look like.
That exploration produced a new Signal-first state management package:
MiniRx Signal Store
- Signal Store is a state management solution built for Angular-only applications
- Signal Store relies on Angular Signals and uses modern Angular APIs under the hood
- Signal Store encourages and encodes new Angular best practices:
- Signals handle your synchronous state
- RxJS takes care of events and asynchronous operations
- Signal Store bridges the gap between RxJS and Signals: methods like
connectandrxEffectaccept both Signals and Observables interchangeably - Signal Store builds on the same proven foundation as the original (RxJS-based) MiniRx Store
- It is an all-in-one package covering global and local state, from trivial to complex scenarios
- You get three clearly separated state containers: Store (Redux), Feature Store, and Component Store
- Maximum flexibility: working on a project that mixes simple and complex features? You can pick the ideal state container for each feature independently.
Getting Started
Requirements
- Angular >= 16
- RxJS >= 7.4.0
Install
Pull down the @mini-rx/signal-store package using your preferred package manager:
npm install @mini-rx/signal-store
API documentation
The full MiniRx Signal Store API reference lives in the README.
Modern Angular Best Practices in MiniRx Signal Store
The MiniRx Signal Store is built entirely with new Angular best practices.
Along the way, the library also advocates for these same practices in its public API:
- Signals handle your synchronous state
- RxJS handles events and asynchronous operations
Let's dig deeper into why this split makes sense...
Signals
Angular 16 gave us Signals as a brand-new reactive primitive. Up until then, RxJS was the dominant approach for reactive state management.
Why Signals?
Angular Signals bring a few clear benefits over RxJS:
- No more manual subscriptions — and no dependency on the
asyncpipe either - Lower learning curve (no pipe, no operators, and Signals are always synchronous)
- Deriving state from other Signals via
computedis simpler than working with RxJScombineLatest - Signals open the door to more granular and efficient Angular Change Detection in the future
Signals in MiniRx Signal Store
It's hard to argue against Signals as the new go-to for state!
MiniRx Signal Store commits fully to that idea: it uses Angular Signal internally and offers Signals throughout its public API:
- The global state object for the Redux Store (shared by the Feature Store) is stored as an Angular Signal
- In the public API, you'll find that every state container has a
selectmethod, which returns an Angular Signal - Memoized selectors pulled from the global state object are built with Angular
computed
RxJS
You might wonder: Do Signals make RxJS obsolete?
For state management, the answer is yes — it is time to move on from BehaviorSubject!
However, RxJS remains invaluable when it comes to events and asynchronous workflows.
Distinct events with RxJS Subject
Signals fall short for event handling, as events can easily be missed. Consider this example with Angular's effect:
One might assume that every state change would be logged inside effect, but that is not what happens when Signal state updates are synchronous...
An RxJS Subject proves to be the superior choice: it delivers all events, including those that occur synchronously. Take a look at this demonstration:
Side effects and race-conditions
With RxJS-based streams, initiating side effects such as API calls becomes straightforward. Race conditions can be managed effectively using RxJS flattening operators (mergeMap, switchMap, concatMap, exhaustMap).
More operators
The possibilities are endless! RxJS provides over 100 operators for stream manipulation.
In practice, however, a modest set will cover most needs: debounceTime, distinctUntilChanged, map, filter, catchError, and others.
RxJS in MiniRx Signal Store
You can see the advantages of RxJS, right?
MiniRx Signal Store has embraced this: RxJS handles events and asynchronous tasks.
- The Action stream within the (Redux) Store is an event stream, built on an RxJS Subject
- Effects can
pipethe Action stream to make API calls, using flattening operators to manage race-conditions - The
rxEffectmethods in Feature Store and Component Store rely on RxJS Subject
An interesting detail: the Component Store also integrates a compact Redux pattern, complete with its own Action stream based on RxJS Subject.
RxJS and Signal Interop
MiniRx Signal Store aims to simplify how you work with RxJS Observables and Signals.
The objective is to remove any need for conversion logic in your codebase, making toSignal and toObservable obsolete.
These MiniRx Signal Store APIs are designed to work with both Observables and Signals:
rxEffect
rxEffect enables side effects, like API calls, within Feature Stores and Component Stores.
There are three ways to kick off a side effect:
- Using a raw value
- Using a Signal
- Using an Observable
This (Component Store) example uses an Angular Signal (Input) to initiate the API call:
import { Component, inject, input, Signal } from '@angular/core';
import { createComponentStore, tapResponse } from '@mini-rx/signal-store';
import { switchMap } from 'rxjs';
import { BookService } from '../book.service';
type State = {
detail: BookDetail;
isLoading: boolean;
}
const initialState: State = {
detail: undefined,
isLoading: false
}
@Component({
// ...
})
export class BookComponent {
private store = createComponentStore(initialState);
private bookService = inject(BookService);
bookId = input.required<string>(); // Signal Input
bookDetail: Signal<BookDetail> = this.store.select(state => state.detail);
isLoading: Signal<boolean> = this.store.select(state => state.isLoading);
// Create an Effect
private loadDetail = this.store.rxEffect<string>(
// Handle race-condition with switchMap
switchMap(id => {
this.store.setState({isLoading: true});
return this.bookService.getBookDetail(id).pipe(
tapResponse({
next: (detail: BookDetail) => {this.store.setState({detail})},
error: () => this.store.setState({isLoading: false})
})
)
})
)
constructor() {
// Fetch detail for every new bookId Signal value
this.loadDetail(this.bookId)
}
}
connect
Available within Feature Store and Component Store.
The connect method allows you to link your store to external data sources, whether they are Observables or Signals.
This approach helps establish your store as the central source of truth for state.
Here we see a Component Store connected to both an Observable and a Signal:
import { Component, Signal, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { createComponentStore } from '@mini-rx/signal-store';
import { timer } from 'rxjs';
@Component({
// ...
})
export class ConnectComponent {
store = createComponentStore({
counter: 0,
counterFromObservable: 0, // Will be updated via Observable
counterFromSignal: 0, // Will be updated via Signal
});
sum: Signal<number> = this.store.select((state) => {
return state.counter + state.counterFromObservable + state.counterFromSignal;
});
constructor() {
const interval = 1000;
const observableCounter$ = timer(0, interval); // Observable
const signalCounter = signal(0); // Signal
// Connect external sources (Observables or Signals) to the Component Store
this.store.connect({
counterFromObservable: observableCounter$, // Observable
counterFromSignal: signalCounter, // Signal
});
setInterval(() => signalCounter.update((v) => v + 1), interval);
}
increment() {
this.store.setState((state) => ({ counter: state.counter + 1 }));
}
}
All-in-one solution
MiniRx Signal Store comes with three distinct state containers ready to use:
- For global state at a larger scale, use the Store (Redux) API
- For global state with minimal boilerplate, use Feature Stores
- For local component-level state, use Component Stores
The complete API is available in the README.
Flexibility
All three state containers work seamlessly together in an application.
You can pick the right container for each specific use-case.
Here are the most common scenarios:
Summary
These are promising times for Angular, as old patterns fade and new ones emerge.
You have seen MiniRx exploring the combination of Signals and RxJS, resulting in the Signal Store.
MiniRx Signal Store is a highly adaptable state management tool:
Whether you are dealing with global or local state, complex or simple, MiniRx Signal Store is equipped to handle it!
By combining flexibility with emerging Angular best practices, MiniRx Signal Store is ready to guide you through modern Angular development.
⭐ MiniRx on GitHub
Enjoying MiniRx? Please show your support with a GitHub star here.
Thank you! :)
Demos
MiniRx Signal Store has been validated in the following projects:
Release
MiniRx Signal Store 1.0.0 is now available!
Thanks
A big thank you goes out to the reviewer of this article:



