Signals (introduced in Angular 16, stable in Angular 17) are a new reactivity primitive that gives Angular fine-grained change detection without Zone.js.
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ double() }}</p>
<button (click)="increment()">+</button>
<button (click)="reset()">Reset</button>
`
})
export class CounterComponent {
count = signal(0); // writable signal
double = computed(() => this.count() * 2); // computed (read-only)
constructor() {
// effect runs whenever any read signal changes
effect(() => console.log(`Count is now: ${this.count()}`));
}
increment(): void { this.count.update(c => c + 1); }
reset(): void { this.count.set(0); }
}
const name = signal('Alice');
name.set('Bob'); // replace value
name.update(v => v + '!'); // transform current value
name(); // read value — call it like a function
@Injectable({ providedIn: 'root' })
export class CartService {
private items = signal<CartItem[]>([]);
readonly count = computed(() => this.items().length);
readonly total = computed(() => this.items().reduce((s, i) => s + i.price, 0));
add(item: CartItem): void {
this.items.update(items => [...items, item]);
}
remove(id: number): void {
this.items.update(items => items.filter(i => i.id !== id));
}
}