Angular is gaining momentum like never before. Its pace of development is impressive, as confirmed by the introduction of many groundbreaking features in its latest 16 version. The hottest area of functionality is signals. Everyone is talking about them and publishing.
The Angular team is doing an excellent job of pushing and explaining this new reactive primitive. So what are signals and do they have a chance to replace RxJS? Let's start with the RxJS library.
By definition, RxJS is a library for reactive programming, Angular uses it to help with handling asynchronous operations and data flow between components. Angular has introduced many developers to RxJS, which is widely used throughout the framework. One prominent example is its usage in the HttpClient, where the response of an HTTP call is exposed through an Observable. Additionally, Angular FormControls have a property called valueChanges, which is a multicasting observable that emits events whenever the control's value changes, whether it's through user interaction or programmatic updates.
When combined with Angular's OnPush change detection strategy and the AsyncPipe utility, which directly binds Observables to templates, these reactive pipelines have become a crucial pattern for building high-performance, race-condition-free, and declarative Angular applications.
However, the most fundamental drawback of RxJS streams (Observables) is the absence of the current value of the stream. Only Behaviors (and Angular's signals) consistently possess a value. Although RxJS's BehaviorSubject is the closest approximation to a signal, it is not seamlessly integrated into RxJS. Once it is piped through an operator like map(), it transforms into an Observable and loses its connection as a BehaviorSubject with a persistent current value.
Furthermore, RxJS offers a wide range of powerful operators that can be utilized to manipulate, combine, debounce, and more on streams. However, for beginners, this power comes with a steep learning curve. The Angular team required something more focused to construct their reactive foundation.
It is important to note that this doesn't imply that RxJS is no longer relevant or useful within Angular. RxJS excels at enabling developers to declare asynchronous, reactive streams. It allows for listening to input change events, debouncing values, using them as parameters for HTTP calls, and mapping the response to the desired model for the view, all in a single location. These capabilities are simply not achievable with signals.
Ultimately, the key point is that while both RxJS and signals are inherently reactive, they address distinct challenges. They complement each other rather than serving as substitutes. Together, they enable the creation of more powerful and straightforward Angular applications.
What are these signals?
A signal serves as a wrapper around a value and notifies interested consumers when that value undergoes a change. Signals have the flexibility to contain various types of values, ranging from simple primitives to complex data structures.
The value of a signal is accessed through a getter function, allowing Angular to track its usage within the application. In Angular, signals are represented as zero-argument functions (() => T). When invoked, they return the current value of the signal. It's worth noting that executing signals does not trigger any side effects, although it may lazily recalculate intermediate values through lazy memorization.
To create a signal, we use the signal() function with the signal's initial value.
count = signal(1);
In the example mentioned above, a numeric signal was created with a default value of 1. Since the default value is already a number, the signal holds a numeric quantity. Therefore, there is no need for a generic type parameter.
The signal() function returns a WritableSignal<T>. Signals act as getter functions, but the WritableSignal type provides three methods to modify the value:
In summary, a Signal is a reactive value that can be observed, updated, and notified to its dependents.
As already mentioned above signals can be likened to BehaviorSubjects, except without the need for manual subscription and unsubscription. Signals always maintain a current value, remain free from side effects, and exhibit reactivity by ensuring their dependents stay synchronized.
To conclude, it is important to emphasize that signals and RxJS are complementary to each other. Numerous applications that leverage OnPush, RxJS, and the async pipe have already positioned themselves to harness the performance benefits offered by signals. In this context, the async pipe will be substituted with signals, while OnPush will become obsolete as signals efficiently notify the framework whenever a DOM update is required. RxJS will continue to excel in its primary role of modeling intricate, asynchronous streams in a declarative manner.
By combining the strengths of signals and RxJS, they will jointly empower the future of Angular applications, enabling enhanced performance and streamlined development.
Angelika Janicka