Signals vs RxJS¶
They are complementary, not rival teams.
| Question | Signal | Observable |
|---|---|---|
| What is the current value? | natural | may require replay/current-value convention |
| Synchronous derivation | computed |
operator pipeline works but may be heavier |
| Async sequence over time | limited alone | core strength |
| Cancellation/concurrency | not encoded by value | higher-order operators encode policy |
| Template read | call signal | AsyncPipe or bridge |
| Error/completion channels | no | yes |
| Dependency tracking | automatic on reads | explicit stream graph |
Choose signals when¶
- state has a meaningful current synchronous value;
- UI derives values from other state;
- local/store updates are commands;
- fine-grained template consumption helps clarity.
Choose RxJS when¶
- multiple async events must be combined;
- cancellation, ordering, buffering, retries, or time matters;
- integrating router, forms, WebSocket, or HTTP streams;
- error and completion semantics are meaningful.
Bridge at ownership boundaries¶
Use toSignal when a component/store owns an observable and consumers need current state. Use toObservable when a signal must enter temporal operator composition. Avoid repeated back-and-forth conversion.
Common mistakes¶
- using an effect to copy one signal into another;
- subscribing inside subscribing instead of selecting flattening semantics;
- turning every event into a global subject;
- keeping server cache as an unbounded root signal without invalidation;
- claiming observables are obsolete because signals render conveniently.
Decision challenge¶
Before reading the example, classify completion percentage, typeahead requests, server cache, route parameters, and a websocket feed by owner, current-value semantics, time, error, completion, and concurrency needs. Implement one boundary in Signals and state and RxJS reasoning.
A strong explanation starts with semantics, not preference: “Completion percentage is synchronous derived state, so computed is appropriate. Typeahead requests require debounce and cancellation, so RxJS with switchMap expresses the policy. I bridge the result once into signal-based view state.”