Skip to content
diggn.de
← all posts Angular & TypeScript · · 2 minutes

Signals instead of RxJS — what I do differently after three months

I moved a mid-sized part of an app from RxJS streams to signals. The code got shorter, but it did not get better everywhere.

The starting point was a feature where six observables met in a combineLatest to fill a table. It worked. It was just that nobody understood it any more, myself included, after two weeks of not looking at it.

What got better immediately

Derived state now reads like a formula rather than a pipeline. A chain of map, filter and shareReplay becomes a computed you can read in one breath:

table.component.tsTypeScript
readonly visibleRows = computed(() => {
  const term = this.searchTerm().trim().toLowerCase();
  if (!term) return this.rows();
  return this.rows().filter((row) => row.title.toLowerCase().includes(term));
});

No subscription, no teardown, no async pipe in the template. And the part that matters most: the value is always there. I no longer have to wonder whether anything had been emitted by the first render.

Signals are good at describing state. RxJS is good at describing events over time. Most of the mistakes happen where you force one to do the other's job.

Where I stayed with RxJS

Everything that involves time. Typing with debounceTime, requests that should cancel each other, retries with backoff. You can rebuild that with signals, but it gets longer and harder to read — you end up hand-writing what switchMap says in one word.

My rule now: the edge of the application speaks RxJS, the core speaks signals. toSignal is the border between them, and you cross it once, not five times.

What I underestimated

effect() is tempting and almost always the wrong answer. Three times I wrote an effect that set another signal — handing myself back exactly the opacity I had set out to remove. If a value follows from other values, it is a computed. An effect is for the outside world: logging, localStorage, repainting a canvas.

Next time I will start from the computed values and only then look at where a stream is genuinely still needed.

#angular#typescript#signals