SCROLL TO EXPLORE •SCROLL TO EXPLORE •
Old getters to new computed signals

Old getters to new computed signals

The secrets of Angular, new computed signals versus old getters

Maksym Byelous - 3 min read

Old getters to new computed signals

I distinctly remember when I met my first getter in an Angular application. At that time it looked to me like pure magic. Functions return the result of some calculations whenever changes are applied to values - magic right? It could be based on input changes or by the act of assigning a new value to a variable, either way, the getter is triggered, recalculation happens and the returned value of the getter is as good as new. Happiness levels of us developers are at an all-time high at this moment, it seems to be all we want, automatic recalculations.

I was happy for a while... until the moment when I was not. One nice day I got knocked with the reality check of how getters work under the hood.

"Why do getters get triggered"

Getters how?

Getters trigger recalculations not based on specific variable changes, but on many other changes happening around in the digital world of your application. Because of this, you will see that most of the time getters are called because of unrelated changes. It could be triggered by scrolling, for example. I have a real world application example of a simple 300 lines component where one getter is triggered around 260 times when the component is visible on the page. This is something that really could negatively impact the performance of your application. Of course it also depends on the complexity of the specific component and the page where it is used. I was no longer happy with the getter solution, so I asked myself if I was able to do anything to improve this.

Change detection strategies

Some people recommend using onPush change detection, but this defeats the purpose since now we need to handle change in the component manually. And we want the exact opposite, automatic value updates. In case you are okay with this, onPush still does not change the fact that getters could be triggered by an unrelated change, when manually marking our component for check.

Computed signals

This is where I looked into computed signals, we know that computed signals are called specifically based on a change of a signal that it depends on. So maybe, it is our saviour.

Let's check a simple example:

export class AngularGetterComponent {
  // Getter data
  dataSet1: number[] = [1];
  dataSet2: number[] = [2];

  get accDataByGetter(): number[] {
    console.log('getter');
    return [...this.dataSet1, ...this.dataSet2];
  }

  updatePropDataSet(num: number) {
    this.dataSet2.push(num + 1);
  }

  // Signal data
  dataSet3 = signal<number[]>([3]);
  dataSet4 = signal<number[]>([4]);

  accDataBySignal = computed<number[]>(() => {
    console.log('signal');
    return [...this.dataSet3(), ...this.dataSet4()];
  });


  updateSignalDataSet(num: number) {
    this.dataSet4.update((prev) => [...prev, num]);
  }
}

It is very basic, just one component which logs every time when we trigger the getter or computed signal, and both values are displayed in html.


<section>
    <span>Getter list:</span>
    @for(getItem of accDataByGetter; track $index) {
        <button type="button"
                (click)="updatePropDataSet(getItem)">
            {{ getItem }}
        </button>
    }
</section>
<section>
    <span>Computed list:</span>
    @for(sigItem of accDataBySignal(); track $index) {
        <button type="button"
                (click)="updateSignalDataSet(sigItem)">
            {{ sigItem }}
        </button>
    }
</section>

Live example can be found here: Getter - signal example

First thing we could notice in the console is that during initiation of the component the getter is called a couple of times whereas the computed signal does only trigger once.

When we update a signal, which is tracked by computed, we see that the computed signal does trigger once, but our unrelated getter is triggered again a few times. We don’t want the getter to do anything when we change the signal.

When trying to update values of data properties used for getters, we see that the getter is called many times again. But most importantly, the computed signal in this case does nothing! The computed signal eliminates unnecessary calculations, finally everything goes the way it should, magic.

Conclusion

Even if there is an earthquake happening, the computed signal does not care about it, it sits and waits until its buddy signal changes. I’d recommend we check our components for usage of getter/setter to be sure it does not do extra unexpected work. It's a real major advantage of signals we could already have today, so don’t wait!

Photo of Maksym Byelous

Maksym Byelous

Senior Software Engineer