angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
95.91k stars 25.35k forks source link

computed efficieny improvement #56269

Open bellostom opened 4 months ago

bellostom commented 4 months ago

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

Hi Right now, computed re evaluates every time any of the underlying signal's properties change, regardless of whether the computed is accessing this property or not, which leads to unnecessary computations and possible re renders.

Coming from MobX, which also has computed but works as described above, makes computed very efficient and performant. I hope you can consider this improvement, since especially in expensive computations will have a major impact in performance.

Below is an example of the scenario I described https://stackblitz.com/edit/stackblitz-starters-pjevlf?file=src%2Fapp%2Fservice.ts

Every time the signal's name updates, the computed that calculates the total price re evaluates, although the computed only reads the price property and never accesses the name property.

Regards

Proposed solution

computed should re evaluate only when the signal properties it accesses, change.

Alternatives considered

None

jnizet commented 4 months ago

There is an alternative though: creating intermediary signals.

  const prices = models.map(m => computed(() => m().price));

  totalPrice = computed(() => {
    console.log('re calculating');
    return prices.reduce((acc, curValue) => {
      return acc + curValue();
    }, 0);
  });
bellostom commented 4 months ago

Hi @jnizet and thanks for the feedback. That is a nice alternative I did not consider. However, the example is a simple one to demonstrate the issue. In case the computed reads several properties and from different signals, it becomes complicated to manage.

JeanMeche commented 4 months ago

This is the way the signal graph works. Every time a signal value changes, the computed will be re-computed if it is consumed.

The name you're updating is not a signal, you're update the parent which is a signal.

This is why JB's solution is what you are looking for.

eneajaho commented 4 months ago

Or you can use untracked I guess to untrack signals you don't want.

bellostom commented 4 months ago

Or you can use untracked I guess to untrack signals you don't want.

This cannot work if the property read in the computed, is part of the same signal object, as demonstrated in my original post, unless I am missing something, since I am new to signals

bellostom commented 4 months ago

This is the way the signal graph works. Every time a signal value changes, the computed will be re-computed if it is consumed.

The name you're updating is not a signal, you're update the parent which is a signal.

This is why JB's solution is what you are looking for.

That is why I am referring to MobX, which does exactly that. It only re caclulates, if one of the property read, actually changes, which is very efficient. Now, I do not know the internals of signals and maybe this cannot be achived, but I would appreciate if someone from angular's team can shed some light on this

JeanMeche commented 4 months ago

Signals are wrapper around values/references. We you read a nested property, you're effectively reading a regular JS property. There is no side effect to expect from this.

This is also why if you mutate an object wrapped by a signal, your signal won't be mark as dirty (and notify its consummers).

bellostom commented 4 months ago

Signals are wrapper around values/references. We you read a nested property, you're effectively reading a regular JS property. There is no side effect to expect from this.

This is also why if you mutate an object wrapped by a signal, your signal won't be mark as dirty (and notify its consummers).

What you are describing is how the feature is currently functioning, which I already understand. I have created this as a feature request, based on how another library, offer similar functionality to signals, is functioning and which is highly efficient.