1

A concept that illudes me about Angular signals - conditional use of signals inside effects:

effect(() => {
    const count = this.outsideFlag ? this.total() : this.current();
    console.log(`The count is: ${count}`);
});

Above we make conditional use of two signals - total + current. And because it is conditional, Angular is going to fail to detect a later change on both signals (will do only for one that executed on the first run).

Does this not severely undermine the whole concept of signals and effects? And how are we supposed to counter such a shortcoming in the change detection?

UPDATE

Consider effect calling a class method, which in turn makes conditional use of signals. This also won't work, but worse - you cannot design class methods based on whether or nor they will be invoked from within effect. This makes implementation inside effects very error-prone. And you cannot address this issue through automation tests either.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • 3
    it seems it should work ok if `this.outsideFlag()` will be a signal as well – Andrei May 23 '23 at 08:22
  • @Andrei That's true. But in my cases it is just a variable. And effects can easily get quite complex, and depend on various external variables that provide conditional execution, and then you end up with signal changes being not detected. It is not how Angular itself works (it picks up all changes inside a template), more like Angular on a limb. – vitaly-t May 23 '23 at 08:26
  • 1
    Well, then you can do something like that: `const totalCount = this.total(); const currentCount = this.current(); const count = this.outsideFlag ? totalCount : currentCount;` – Eldar May 23 '23 at 09:22
  • @Eldar This is hardly usable. Consider the case when effect invokes a class method, which in turn uses signals. You cannot design method implementation based on whether on not they will be called from within effect, it would be extremely error-prone. – vitaly-t May 23 '23 at 09:30
  • 2
    Well, the `effect` API is a design change and it is still tagged as `developer preview`, you might want to open an issue on the repository. But as it is a design change, it might not fit your previous application design, also IMHO it is still too early to jump in the `signal` train :) – Eldar May 23 '23 at 09:56

2 Answers2

1

effect() and compute() are in principle pretty similar. The difference is that effect() doesn't return a signal object. I mention this because the signal documentation addresses conditionals in context of compute(). This is their example:

const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
  if (showCount()) {
    return `The count is ${count()}.`;
  } else {
    return 'Nothing to see here!';
  }
});

When reading conditionalCount, if showCount is false the "Nothing to see here!" message is returned without reading the count signal. This means that updates to count will not result in a recomputation.

If showCount is later set to true and conditionalCount is read again, the derivation will re-execute and take the branch where showCount is true, returning the message which shows the value of count. Changes to count will then invalidate conditionalCount's cached value.

Note that dependencies can be removed as well as added. If showCount is later set to false again, then count will no longer be considered a dependency of conditionalCount.

Their "solution" is, as @Andrei in the comments explain, to make the conditional a signal too:

effect(() => {
    const count = this.outsideFlag() ? this.total() : this.current();
    console.log(`The count is: ${count}`);
});

A change in outsideFlag() will make the effect recompute, invoking the other signal. This is a feature-not-a-bug, as it saves computation. If it wasn't like this, you would have to recompute all branches for all changes. (Kind of like @Eldar in the comments suggestion.)

As for cases where the conditional is hidden:

Consider the case when effect invokes a class method, which in turn uses signals.

The conditionals in those class methods should also be signals.

You should use signals all-the-way-down.

André C. Andersen
  • 8,955
  • 3
  • 53
  • 79
  • I still do not see it as a feature, rather a limitation. I tried to suggest a solution to it [here](https://github.com/angular/angular/issues/50435), by allowing an explicit list of signals for watching, but it didn't get any traction. – vitaly-t Jun 15 '23 at 21:50
0

Take it to the other extreme. NO signals in your effect. What mechanism could possibly detect a change in ANY of the properties used? Could you ever expect it to? If you do you just invented regular Angular change detection! You just can’t have it both ways.

With your example:

this.outsideFlag ? this.total() : this.current();

Even if you could provide dependencies (total and current) to the effect it wouldn’t even help.

Consider this sequence

  • outsideFlag starts as true
  • so value of total is returned
  • value of total is changed outside
  • effect runs again
  • current is set to a new value
  • no change occurs or should occur
  • flag is changed to false
  • angular has no way to detect it
  • the fact we were ‘watching’ current didn’t help at all.

You’re either using signals or you’re not. And yes this kind of issue can lead to all kinds of ‘bugs’ or things that don’t get updated. So when you find the bug you have to make it a signal. There’s no other way to get the performance improvements promised by signals.

At the end of the day it’s the responsibility of the developer to understand the toolset.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689