A lot of what you show on screen is calculated from other values: a total from prices and quantities, a count of finished tasks, a full name from two parts. Computing these by hand each time is error-prone. A computed signal calculates the value for you and keeps it up to date.
What you will learn
- How to create a computed signal
- Why computed values are cached
- What
effect()is for, and what it is not for
Create a computed signal
computed() takes a function. Any signals that the function reads become its dependencies:
import { computed, signal } from '@angular/core';
const price = signal(500);
const quantity = signal(2);
const total = computed(() => price() * quantity());
console.log(total()); // 1000
quantity.set(3);
console.log(total()); // 1500
You never set total. It is read-only, and it changes when price or quantity changes.
Why computed instead of a method?
A computed signal has two advantages over calling a method in the template:
- It is cached. The calculation only runs again when a dependency changes. Reading it ten times costs the same as reading it once.
- It is lazy. If nothing reads it, it is not calculated at all.
A component with derived values
// src/app/price-box/price-box.ts
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-price-box',
template: `
<h2>Order</h2>
<p>Price: {{ price() }}</p>
<p>Quantity: {{ quantity() }}</p>
<button type="button" (click)="more()">More</button>
<button type="button" (click)="fewer()" [disabled]="quantity() === 1">Fewer</button>
<p>Subtotal: {{ subtotal() }}</p>
<p>Discount: {{ discount() }}</p>
<p><strong>Total: {{ total() }}</strong></p>
`,
})
export class PriceBox {
protected readonly price = signal(500);
protected readonly quantity = signal(1);
protected readonly subtotal = computed(() => this.price() * this.quantity());
protected readonly discount = computed(() => (this.quantity() >= 5 ? this.subtotal() * 0.1 : 0));
protected readonly total = computed(() => this.subtotal() - this.discount());
protected more(): void {
this.quantity.update((q) => q + 1);
}
protected fewer(): void {
this.quantity.update((q) => Math.max(1, q - 1));
}
}
// src/app/app.ts
import { Component } from '@angular/core';
import { PriceBox } from './price-box/price-box';
@Component({
imports: [PriceBox],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<h1>Computed signals</h1>
<app-price-box />
</main>
Click "More" until the quantity reaches five and the discount appears. Notice that total depends on subtotal and discount, which depend on the base signals. Angular works out the chain for you.
Keep computed functions pure
A computed function should only read signals and return a value. Do not change other signals or call the server inside it. If you need to cause a side effect, use something else, as described next.
Effects: reacting with side effects
An effect() runs a function whenever the signals it reads change. It is meant for side effects, which are things outside of your template, such as writing to local storage or logging:
// src/app/theme-saver.ts
import { Component, effect, signal } from '@angular/core';
@Component({
selector: 'app-theme-saver',
template: `
<button type="button" (click)="toggle()">Dark mode: {{ dark() ? 'on' : 'off' }}</button>
`,
})
export class ThemeSaver {
protected readonly dark = signal(false);
constructor() {
effect(() => {
console.log('Dark mode changed to', this.dark());
});
}
protected toggle(): void {
this.dark.update((value) => !value);
}
}
An effect must be created where Angular can track it, such as a constructor or a field initializer of a component. It runs at least once, and then whenever dark() changes.
Try it yourself
- Add a
taxRatesignal and ataxcomputed value, and include tax in the total. - Add a computed that returns "Bulk order" when the quantity is five or more.
- Use an effect to save the quantity into
localStoragewhenever it changes.
Quick check
- Can you call
.set()on a computed signal? - When does a computed function run again?
- When would you use an effect instead of a computed signal?
Put these ideas to work in Signals in practice: a shopping cart.