Signals make Angular MUCH easier
Tags: angular, signals, rxjs
Published: 2023-07-18 21:41:30

---
title: Signals make Angular MUCH easier
published: true
description: RxJS has been doing a lot of work in Angular apps since 2016. Now, signals are here to take some of that workload away.
tags: angular,signals,rxjs
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvn15gzzvfgxsgya04xr.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-18 21:26 +0000
series: Signals vs RxJS
---

[YouTube](https://www.youtube.com/watch?v=FRCpN62LyyM)

Why shouldn't RxJS do everything?

RxJS is amazing, but it has limitations. Consider a counter implemented using a simple variation of the "Subject in a Service" approach:

```tsx
export class CounterService {
  count$ = new BehaviorSubject(0);

  increment() {
    this.count$.next(this.count$.value + 1);
  }
}
```

Now multiple components can share and react to this state by subscribing to `count$`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwle1gbrnwzeqomoh222.gif)

Now let's add some derived states with the RxJS `map` and `combineLatest` operators:

```tsx
  count$ = new BehaviorSubject(1000);

  double$ = this.count$.pipe(map((count) => count * 2));
  triple$ = this.count$.pipe(map((count) => count * 3));

  combined$ = combineLatest([this.double$, this.triple$]).pipe(
    map(([double, triple]) => double + triple)
  );

  over9000$ = this.combined$.pipe(map((combined) => combined > 9000));

  message$ = this.over9000$.pipe(
    map((over9000) => (over9000 ? "It's over 9000!" : "It's under 9000."))
  );
```

Here's a diagram of these reactive relationships:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7c3bmlvlp2rl0aj4gc1.png)

Here's what that looks like:

![Shared Counter State 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/91qvaczkkl2i49vyksww.gif)

Isn't this easy? RxJS just takes care of everything for us. There's probably nothing wrong with this.

Actually there is. Let's put a console log inside the `map` for `message$` and see what happens when we increment the count once.

```tsx
  message$ = this.over9000$.pipe(
    map((over9000) => {
      console.log('Calculating message$', over9000);
      return over9000 ? "It's over 9000!" : "It's under 9000.";
    })
  );
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p43zb98sj07r2ud7m9j.png)

Why did it run 4 times? We only incremented the count once. That's not efficient.

Something weird is going on. Let's put console logs inside each observable so we can get a view into everything happening. And think for a minute about what we should expect. We have a single event, and 5 derived states: `double$`, `triple$`, `combined$`, `over9000$`, and `message$`. Shouldn't we see 5 console logs? Well, here's what we actually get:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0txtr78l23drz3aynu3l.png)

It's over 9000!!! We just implemented our feature in the simplest way possible, and this is what RxJS gave us. This is 40 logs, or 8x what it should be.

We need to understand how subscriptions work. We have 2 components subscribing to several of these observables. Here I've added a colored line for each subscription:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1099j60o6cg53p0sk8oe.jpg)

Each subscription gets passed all the way up to the top of the chain. If you count the number of blue and green lines next to `double$` and `triple`, it's 8 each. That's the number of console logs for each of those. `combined$` has 12 lines around it (because of the branching), and 12 logs. But `message$` has 2 lines and not 2 but 4 console logs, and `over9000$` has 4 lines but 8 console logs. That's because each of those lines ends up splitting into 2 lines up at the `combineLatest`. 

We have to learn more operators to deal with these problems: `map` and `distinctUntilChanged` (sometimes with a comparator), `combineLatest` and `debounceTime`, and `shareReplay`.  Actually, not `shareReplay`, more like `publishReplay` and `refCount`. Or actually, `merge`, `NEVER`, `share` and `ReplaySubject` (more on these later). The really crazy thing is that most people aren't even aware of all these issues. It takes some painful experiences to learn that these operators are necessary.

But asking everyone to avoid the numerous *RxJS* pitfalls, become intimately familiar with how subscriptions work, and learn all these operators, all just for basic derived state, is absurd. And, these operators increase bundle size and do work at runtime. Creating a custom operator doesn't fix that. 

So, while RxJS is amazing for managing asynchronous event streams, it is inefficient and difficult to use for synchronizing states.

## How about selectors?

Selectors are pretty efficient at computing derived states.

But I never liked their syntax:

```tsx
createSelector(
  selectItems,
  selectFilters,
  (items, filters) => items.filter(filters),
);
```

So for [StateAdapt](https://state-adapt.github.io/angular) I came up with new syntax:

```tsx
buildAdapter<State>(...)({
  filteredItems: s => s.items.filter(s.filters),
})();
```

But selectors require a state management library with a global state object, which makes them impossible to integrate tightly with framework APIs, such as component inputs.

## Signals

Angular needed a reactive primitive of its own, and out of all the options, signals were the best choice for synchronization.

Let's implement our counter with Angular signals:

```tsx
  count = signal(1000);

  double = computed(() => this.count() * 2);
  triple = computed(() => this.count() * 3);

  combined = computed(() => this.double() + this.triple());

  over9000 = computed(() => this.combined() > 9000);

  message = computed(() =>
    this.over9000() ? "It's over 9000!" : "It's under 9000."
  );
```

Now when we click, we get the 5 expected logs:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cof5jr8tsd00mpxlq5vh.png)

It's more efficient than even optimized RxJS, and we only needed one "operator": `computed`. 

The Angular team did an amazing job with the implementation, too. If you want to learn more about how it works, I recommend [this interview](https://www.youtube.com/watch?v=HstDoVQeP9g&t=10251s) they did with Ryan Carniato. 

## Problems with signals

Signals are awesome, but like RxJS, they have limitations:

1. Asynchronous Reactivity
2. Eager & Stale State

These will be the topics of my next articles.