Angular Signals: Best Practices
Tags: angular, webdev, signals, reactivity
Published: 2024-04-01 10:20:40

In this article, I share my experience of working with Angular Signals after almost a year of using them.

### When to use Signals?

 1. In templates;
 2. When you need to react to changes in a value without a time aspect.

In Angular templates, Signals are better than Observables: they schedule Change Detection without any pipes, they are glitch-free, and you can read the same signal multiple times and it will be “free” in terms of performance (and read values are guaranteed to be the same). There are other reasons that are not so easy to explain briefly, but that’s already enough to make a rule: every variable (that might change) in your **new** templates should be a Signal.

Outside of templates, Signals also can be used for reactivity, but, as I mentioned, without a time aspect.

I once wrote a post on Twitter about it, and now I’ll post it here, updated and improved:

There are two ways to create reactive variables in Angular: Observables and Signals. If you describe in words, how your variable should express its reactivity, you’ll see what you need to use.

If the role of a variable can be described as conditions, then you need a Signal:

* “if this variable has this value, then display this list”
* “if this variable has this value, this button is disabled”

If the description of a variable’s role includes words, related to time, you need an Observable:

* “**when** the cursor moves…”
* “**wait** for the file uploading event and then…”
* “every **time** this event happens, do this…”
* “**until** this event…”
* “for N **seconds** ignore…”
* “**after** this request…”

Signals have no time axis, and they can not delay a value — they always have a value, and their consumers should be always able to read it.

Consumers of Signals, `computed()`, `effect()`, and templates do not guarantee that they will read every new value written to the Signals they watch. An updated Signal will be *eventually* read, not instantly after the update as it happens with Observables. Consumers decide when they will read the new value using their scheduling mechanisms. It might be “in the next task,” “during the next Change Detection cycle,” or at some other moment, up to the consumer.

### When to use `computed()`?

Whenever you like!

`computed()` is the best thing in Angular Signals, incredibly handy and safe to use. Using `computed()`, you’ll make your code more declarative (you can read more about it in [this article](https://medium.com/@eugeniyoz/creating-angular-components-template-first-declarative-approach-00c4a4791270)).

There are just two rules about the usage of `computed()`:

 1. Do not modify things in `computed()`. It should compute a new result, that’s it. Do not modify the DOM, do not mutate variables using `this`, and do not call functions that might do that. Do not push values to Observables — it will cause unintentional reactive context propagation (explained below for `effect()`). `computed()` should not have side effects, it should be a [pure function](https://en.wikipedia.org/wiki/Pure_function).
 2. Do not make asynchronous calls in `computed()`. This function does not allow modification of Signals (and it is amazingly helpful), but it can not track asynchronous code. Moreover, Angular Signals are strictly synchronous, so if you want to use asynchronous code in `computed()`, you are doing something wrong. So, no `setTimeout()`, no Promises, no other asynchronous things.

### When to use `effect()`?

Angular docs say that you’ll rarely need `effect()` and [discourage](https://angular.dev/guide/signals#use-cases-for-effects) you from using it ([copy](https://gist.github.com/e-oz/99d04094abe5007d882f35879eb00da3), if docs will be edited).

And that info is correct: you rarely need `effect()`… if your code is declarative ;)

The more imperative your code, the more often you’ll need `effect()`. There is no code without imperative parts, but we all should try to make our code as declarative as possible, so we do need to use `effect()` as rarely as possible.

Besides the dangers, mentioned by Angular docs (infinite loops, change detection errors), there is another thing, that might be quite nasty: effects are executed in a reactive context, and any code you call in effect, will be executed in a reactive context. If that code reads some signals, they will be added as dependencies to your effect. [Here](https://github.com/angular/angular/pull/54614) Alex Rickabaugh explains the details.

I still don’t want to encourage you to use `effect()`, but I’ll give you advice on how to use it as safely as possible:

 1. The function you provide to `effect()` should be as small as possible. This way it will be easier to read and spot erroneous behavior.
 2. Read signals first, then wrap the rest of the effect into `untracked()`:
```ts
    effect(() => {
      // reading the signals we need
      const a = this.a();
      const b = this.b();
      
      untracked(() => {
        // rest of the code is here - this code should not
        // modify the signals we read above!
        if (a > b) {
          document.title = 'Ok';
        }
      });
    });
```

### Mixing Signals and Observables

…is ok!

Your code will have Signals and Observables, at least because Signals can not be used for every kind of reactivity (see detailed explanation above). It is not an issue, it is perfectly fine.

If you need some value from an Observable in your `computed()`, create a Signal using `toSignal()` (outside of `computed()`).

If you need to read a Signal in Observable’s `pipe()`, there are two ways:

 1. If you need to react to changes in that Signal in your Observable, then you need to convert that Signal into Observable and add it using some join operator.
 2. If you are sure you just need the current value of a Signal and you don’t need to react to its changes — you can read a Signal directly in your operators or `subscribe()`. Observable is not a reactive context, so you don’t need `untracked()` here.
 
---

🪽 Do you like this article? Share it and let it fly! 🛸

💙 *If you enjoy my articles, consider [following me on Twitter](https://twitter.com/eugeniyoz), and/or [subscribing](https://medium.com/@eugeniyoz) to receive my new articles by email.*

---

{% embed https://twitter.com/eugeniyoz/status/1770250842974695912 %}