Skip to the solution
MediumFrontend9 min read

What is the difference between computed() signals and standard method/getter calls in templates?

841 views

The problem

Question presented to candidate: "A teammate wrote {{ totalPrice() }} where totalPrice is a class method that loops over the cart. Another wrote the same thing as a computed() signal. Both render the same number. What is the actual difference, and does it matter in Angular 22?"

What a strong answer should cover:

  • A method or getter in a template is called on every check of that view, whether or not its inputs changed; it caches nothing.
  • A computed() is memoised: it re-runs only when a signal it read changed, and returns the cached value otherwise.
  • With OnPush (the default in v22) a view is only checked when marked dirty, so method calls are bounded by how often the view is checked; with Eager (the old Default) they run on every pass that reaches it.
  • computed also cuts off propagation: if its result is unchanged, downstream computeds do not re-run.
  • A method that only reads signals is still correct, just not cached; the concern is cost and side effects, not correctness.
  • Alternatives for non-signal data: a pure pipe.

Clarifying questions expected:

  • "Is the data in signals already, or plain fields or observables?"

Code / implementation expected: Yes β€” count how often each runs across change detection cycles.

  • signals
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularMethod vs getter vs computed: call counts across view checks
import { Component, computed, signal } from "@angular/core";

const calls = { method: 0, getter: 0, computed: 0 };

@Component({
  selector: "app-root",
  template: `
    <p>method:   {{ totalMethod() }}</p>
    <p>getter:   {{ totalGetter }}</p>
    <p>computed: {{ totalComputed() }}</p>
    <p>clicks: {{ clicks() }}</p>
    <button (click)="clicks.update(n => n + 1)">Unrelated click</button>
    <button (click)="addItem()">Add item</button>
    <button (click)="report()">Report</button>
  `,
})
export class AppComponent {
  items = signal([10, 20, 30]);
  clicks = signal(0); // unrelated state that lives in the same template

  // 1. A method: re-run every time this view is checked.
  totalMethod() {
    calls.method++;
    return this.items().reduce((a, b) => a + b, 0);
  }

  // 2. A getter: just a method without parentheses. Same behaviour.
  get totalGetter() {
    calls.getter++;
    return this.items().reduce((a, b) => a + b, 0);
  }

  // 3. A computed: re-run only when items() changes, cached otherwise.
  totalComputed = computed(() => {
    calls.computed++;
    return this.items().reduce((a, b) => a + b, 0);
  });

  addItem() {
    this.items.update(list => [...list, 5]);
  }

  report() {
    console.log(`  method=${calls.method} getter=${calls.getter} computed=${calls.computed}`);
  }
}
AngularThe same method in an OnPush child and an Eager child
import { ChangeDetectionStrategy, Component, signal } from "@angular/core";

const calls = { onPush: 0, eager: 0 };

// Default in v22: OnPush. Checked only when something marks THIS view dirty.
@Component({
  selector: "onpush-total",
  template: `<p>OnPush child: {{ total() }}</p>`,
})
export class OnPushTotalComponent {
  total() {
    calls.onPush++;
    return 42;
  }
}

// Opt-in: Eager (the old "Default"). Checked on every pass that reaches it.
@Component({
  selector: "eager-total",
  changeDetection: ChangeDetectionStrategy.Eager,
  template: `<p>Eager child: {{ total() }}</p>`,
})
export class EagerTotalComponent {
  total() {
    calls.eager++;
    return 42;
  }
}

@Component({
  selector: "app-root",
  imports: [OnPushTotalComponent, EagerTotalComponent],
  template: `
    <onpush-total />
    <eager-total />
    <button (click)="clicks.update(n => n + 1)">Parent click {{ clicks() }}</button>
    <button (click)="report()">Report</button>
  `,
})
export class AppComponent {
  clicks = signal(0);
  report() {
    console.log(`  OnPush child method calls=${calls.onPush}  Eager child method calls=${calls.eager}`);
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 29 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

What is the role of NgRx Effects, and why do we separate side effects from reducers?

Continue