Angular Signals e Computed nel 2026: Reattività Fine-Grained e Domande per Colloqui
Una guida approfondita ad Angular Signals, Computed Signals e al modello reattivo in Angular 20+. Include best practice, interoperabilità con RxJS e domande frequenti nei colloqui tecnici.

Angular Signals rappresentano il cambiamento più significativo nel modello di reattività di Angular dalla nascita del framework. Introdotti in Angular 17 e ora l'approccio predefinito in Angular 20+, i signals forniscono una reattività fine-grained che elimina cicli di change detection non necessari e si integra perfettamente con le applicazioni zoneless.
I Signals sono primitive reattive sincrone e glitch-free. Un computed signal ricalcola solo quando le sue dipendenze cambiano e solo una volta per ciclo di cambiamento, indipendentemente da quante volte viene letto.
Comprendere le Primitive Signal in Angular 20
Il sistema di signal di Angular è composto da tre primitive fondamentali: signal(), computed() e effect(). Ognuna serve uno scopo distinto nel grafo reattivo.
Un signal scrivibile mantiene un valore che può essere aggiornato con .set() o .update(). La lettura del signal restituisce il valore corrente, e tutti i computed signals o effects che dipendono da esso vengono notificati dei cambiamenti.
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ doubleCount() }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
// Signal scrivibile con valore iniziale
count = signal(0);
// Computed signal deriva da count
// Ricalcola solo quando count cambia
doubleCount = computed(() => this.count() * 2);
increment(): void {
// .update() riceve il valore corrente come argomento
this.count.update(c => c + 1);
}
}La differenza chiave rispetto a RxJS BehaviorSubject: i signals sono sincroni e glitch-free. Quando count cambia, doubleCount ricalcola esattamente una volta, anche se viene letto più volte nello stesso blocco sincrono.
Computed Signals: Lazy Evaluation e Memoization
I computed signals memorizzano il loro risultato nella cache fino a quando una dipendenza cambia. Questa memoization è automatica, a differenza di RxJS dove shareReplay o distinctUntilChanged devono essere aggiunti manualmente.
import { Component, signal, computed } from '@angular/core';
interface User {
id: number;
name: string;
purchases: number[];
}
@Component({
selector: 'app-user-stats',
standalone: true,
template: `
<div>
<p>User: {{ user().name }}</p>
<p>Total spent: {{ totalSpent() | currency }}</p>
<p>Average purchase: {{ averagePurchase() | currency }}</p>
</div>
`
})
export class UserStatsComponent {
user = signal<User>({
id: 1,
name: 'Alice',
purchases: [99.99, 149.50, 29.99]
});
// Computed traccia solo le proprietà che legge
totalSpent = computed(() => {
const purchases = this.user().purchases;
return purchases.reduce((sum, p) => sum + p, 0);
});
// Dipende da un altro computed signal
averagePurchase = computed(() => {
const total = this.totalSpent();
const count = this.user().purchases.length;
return count > 0 ? total / count : 0;
});
}Un computed signal viene valutato in modo lazy: la funzione di calcolo viene eseguita solo quando il signal viene letto per la prima volta, non quando viene dichiarato. Le letture successive restituiscono il valore memorizzato nella cache fino a quando una dipendenza lo invalida.
Effect: Side Effects nel Grafo Reattivo
Gli effects eseguono codice in risposta ai cambiamenti dei signal. Vengono eseguiti in modo asincrono durante il ciclo di change detection, dopo che tutti gli aggiornamenti sincroni sono completati.
import { Component, signal, effect, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'app-theme-sync',
standalone: true,
template: `
<button (click)="toggleTheme()">Toggle Theme</button>
`
})
export class ThemeSyncComponent {
private document = inject(DOCUMENT);
isDarkMode = signal(false);
constructor() {
// Effect sincronizza lo stato del signal con l'attributo DOM
effect(() => {
const theme = this.isDarkMode() ? 'dark' : 'light';
this.document.documentElement.setAttribute('data-theme', theme);
});
// Effect per la persistenza in localStorage
effect(() => {
localStorage.setItem('theme', this.isDarkMode() ? 'dark' : 'light');
});
}
toggleTheme(): void {
this.isDarkMode.update(dark => !dark);
}
}Gli effects tracciano le dipendenze dinamicamente. Se un branch condizionale non viene eseguito, i signals letti solo in quel branch non vengono tracciati fino a quando la condizione cambia.
Usare effect per copiare dati da un signal a un altro indica un problema di design. Preferire computed per lo stato derivato o linkedSignal quando il valore derivato deve essere scrivibile.
linkedSignal: Stato Derivato Scrivibile
Introdotto in Angular 19 e stabile in Angular 20, linkedSignal crea un signal scrivibile che si resetta quando la sua sorgente cambia. Risolve scenari in cui computed è di sola lettura ma il valore derivato necessita di modifica locale.
import { Component, signal, linkedSignal, computed } from '@angular/core';
@Component({
selector: 'app-pagination',
standalone: true,
template: `
<select (change)="pageSize.set(+$any($event.target).value)">
<option [value]="10">10 per page</option>
<option [value]="25">25 per page</option>
<option [value]="50">50 per page</option>
</select>
<p>Page {{ currentPage() }} of {{ totalPages() }}</p>
<button (click)="prevPage()" [disabled]="currentPage() === 1">Prev</button>
<button (click)="nextPage()" [disabled]="currentPage() === totalPages()">Next</button>
`
})
export class PaginationComponent {
totalItems = signal(243);
pageSize = signal(10);
totalPages = computed(() =>
Math.ceil(this.totalItems() / this.pageSize())
);
// linkedSignal si resetta a 1 quando pageSize o totalItems cambia
// ma permette la navigazione manuale via prevPage/nextPage
currentPage = linkedSignal(() => 1);
prevPage(): void {
this.currentPage.update(p => Math.max(1, p - 1));
}
nextPage(): void {
this.currentPage.update(p => Math.min(this.totalPages(), p + 1));
}
}Senza linkedSignal, questo pattern richiedeva un effect che scriveva su un altro signal (anti-pattern) o un'orchestrazione RxJS complessa.
Pronto a superare i tuoi colloqui su Angular?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
API Component Basate su Signal: input, model, viewChild
Angular 20 promuove le API component basate su signal allo stato stabile. Queste sostituiscono i decoratori con alternative reattive che si integrano nel grafo dei signal.
import {
Component,
input,
model,
output,
computed,
viewChild,
ElementRef
} from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div #card class="card" [class.selected]="selected()">
<h3>{{ fullName() }}</h3>
<button (click)="toggle()">{{ selected() ? 'Deselect' : 'Select' }}</button>
</div>
`
})
export class UserCardComponent {
// Signal input sostituisce @Input()
firstName = input.required<string>();
lastName = input<string>('');
// model() crea un signal bindabile a due vie
// Il parent usa [(selected)]="parentSignal"
selected = model(false);
// Output emitter per gli eventi
cardClicked = output<void>();
// viewChild restituisce Signal<ElementRef | undefined>
cardElement = viewChild<ElementRef>('card');
// Computed deriva dai signal input
fullName = computed(() =>
`${this.firstName()} ${this.lastName()}`.trim()
);
toggle(): void {
this.selected.update(s => !s);
this.cardClicked.emit();
}
}I signal input forniscono una migliore type safety: input.required() restituisce InputSignal<T> mentre input() restituisce InputSignal<T | undefined> a meno che non venga fornito un default.
Signals vs RxJS: Performance Sotto Carico
I benchmark in Angular 21+ dimostrano che i signals riducono l'overhead della change detection negli scenari UI-intensive. Sotto carico pesante con aggiornamenti frequenti, i signals hanno mostrato profili di memoria più puliti e un minor utilizzo della CPU rispetto ai binding guidati da RxJS.
La separazione delle responsabilità è chiara:
| Caso d'Uso | Approccio Raccomandato |
|---|---|
| Stato locale del component | signal() |
| Valori UI derivati | computed() |
| Sincronizzazione con API esterne | effect() |
| Richieste HTTP | RxJS Observable + toSignal() |
| Stream WebSocket | RxJS con operatori |
| Input utente con debounce | RxJS debounceTime, poi toSignal() |
| Orchestrazione async complessa | Pipeline RxJS |
RxJS rimane lo strumento per stream async, cancellazione e backpressure. I signals gestiscono lo stato sincrono.
Interoperabilità RxJS: toSignal e toObservable
Angular fornisce utility in @angular/core/rxjs-interop per collegare signals e observables.
import { Component, signal, inject } from '@angular/core';
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { debounceTime, switchMap, distinctUntilChanged } from 'rxjs';
interface SearchResult {
id: number;
title: string;
}
@Component({
selector: 'app-search',
standalone: true,
template: `
<input
[value]="query()"
(input)="query.set($any($event.target).value)"
placeholder="Search..."
/>
@if (results(); as items) {
<ul>
@for (item of items; track item.id) {
<li>{{ item.title }}</li>
}
</ul>
}
`
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// Converti signal in observable per gli operatori RxJS
private query$ = toObservable(this.query);
// Pipeline RxJS con debounce, poi torna a signal
results = toSignal(
this.query$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(q => q.length > 2
? this.http.get<SearchResult[]>(`/api/search?q=${q}`)
: []
)
),
{ initialValue: [] as SearchResult[] }
);
}toSignal si sottoscrive alla creazione e si cancella alla distruzione del component. L'opzione initialValue elimina il tipo undefined dal signal.
Domande per Colloqui: Angular Signals
I colloqui tecnici nel 2026 testano frequentemente la comprensione dei signal. Ecco i pattern che distinguono i candidati senior.
D: Cosa succede se un computed signal lancia un errore?
L'errore si propaga a qualsiasi effect o computed che lo legge. Angular non memorizza gli errori nella cache, il calcolo viene ritentato alla lettura successiva. Il codice di produzione dovrebbe gestire gli errori all'interno della funzione computed.
D: Si può scrivere su un signal all'interno di un computed?
No. Tentare di chiamare .set() o .update() all'interno di un computed lancia un errore. Le funzioni computed devono essere pure. Usare linkedSignal se si necessita di un valore derivato che sia anche scrivibile.
D: Come gestiscono gli effects le letture dei signal nelle condizioni?
// Esempio di tracciamento dinamico delle dipendenze
const showDetails = signal(false);
const userDetails = signal({ name: 'Alice', email: 'alice@example.com' });
effect(() => {
if (showDetails()) {
// userDetails viene tracciato solo quando showDetails è true
console.log('Details:', userDetails().email);
}
});Gli effects tracciano le dipendenze in base all'esecuzione più recente. Se showDetails è false, i cambiamenti a userDetails non attivano l'effect.
D: Come funziona l'uguaglianza dei signal?
Per default, i signals usano Object.is per l'uguaglianza. Per gli oggetti, questo significa uguaglianza per riferimento. Si può fornire un'uguaglianza personalizzata:
const user = signal(
{ id: 1, name: 'Alice' },
{ equal: (a, b) => a.id === b.id }
);I candidati che menzionano che i computed signals sono memoizzati e valutati in modo lazy dimostrano una comprensione oltre l'uso base dell'API. Discutere quando usare linkedSignal vs computed mostra familiarità con i pattern di Angular 19+.
Change Detection: Signals e Angular Zoneless
Angular 22 usa di default la change detection zoneless per i nuovi progetti. I signals sono centrali in questo: notificano Angular precisamente quando lo stato cambia, eliminando la necessità che Zone.js faccia il patch delle API async.
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-zoneless-counter',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">+1</button>
`
})
export class ZonelessCounterComponent {
count = signal(0);
increment(): void {
// L'aggiornamento del signal attiva la change detection
// Zone.js non è richiesto
this.count.update(c => c + 1);
}
}Con i signals, Angular traccia esattamente quali component dipendono da quale stato. Un aggiornamento del signal schedula la change detection solo per i component interessati, non per l'intero albero.
Resource API: Dati Async come Signals
La Resource API di Angular 20 (resource() e rxResource()) carica dichiarativamente i dati async nei signals, sostituendo i pattern di sottoscrizione manuale.
import { Component, signal, computed } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
standalone: true,
template: `
@if (userResource.isLoading()) {
<p>Loading...</p>
}
@if (userResource.error()) {
<p>Error: {{ userResource.error() }}</p>
}
@if (userResource.value(); as user) {
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
}
`
})
export class UserProfileComponent {
private http = inject(HttpClient);
userId = signal(1);
// Resource async dichiarativa
// Ricarica quando userId cambia
userResource = rxResource({
request: () => this.userId(),
loader: ({ request: id }) =>
this.http.get<User>(`/api/users/${id}`)
});
loadUser(id: number): void {
this.userId.set(id);
}
}La resource traccia automaticamente lo stato di caricamento, gli errori e il valore risolto. Ricarica quando il signal di richiesta cambia.
Best Practice per l'Architettura dei Signal
Strutturare i signals in applicazioni più grandi richiede disciplina. Questi pattern scalano dai component ai service.
Raggruppare i signals correlati: Raggruppare i signals che cambiano insieme. Un component form mantiene i suoi field signals insieme, non sparsi tra i service.
Elevare lo stato condiviso ai service: Quando più component necessitano dello stesso stato, spostare il signal in un service iniettabile:
import { Injectable, signal, computed } from '@angular/core';
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
@Injectable({ providedIn: 'root' })
export class CartService {
private items = signal<CartItem[]>([]);
// Esporre un signal di sola lettura ai consumer
readonly cartItems = this.items.asReadonly();
readonly totalPrice = computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
readonly itemCount = computed(() =>
this.items().reduce((sum, item) => sum + item.quantity, 0)
);
addItem(item: Omit<CartItem, 'quantity'>): void {
this.items.update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
return items.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
}
return [...items, { ...item, quantity: 1 }];
});
}
}Preferire computed rispetto a effect: Se un valore può essere derivato, usare computed. Gli effects sono per i side effects che lasciano il grafo reattivo: manipolazione DOM, localStorage, analytics, richieste di rete.
Migrazione da RxJS a Signals
Le codebase Angular legacy possono migrare in modo incrementale. Le utility di interoperabilità RxJS permettono la coesistenza di signals e observables.
// Prima: basato su RxJS
@Component({ ... })
export class LegacyComponent {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
userName$ = this.user$.pipe(
map(u => u?.name ?? 'Guest')
);
}
// Dopo: basato su Signal
@Component({ ... })
export class ModernComponent {
user = signal<User | null>(null);
userName = computed(() => this.user()?.name ?? 'Guest');
}Per le operazioni async, mantenere RxJS per la pipeline e convertire al confine:
// Approccio ibrido
private search$ = toObservable(this.searchQuery).pipe(
debounceTime(300),
switchMap(q => this.api.search(q))
);
results = toSignal(this.search$, { initialValue: [] });Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Prepararsi alle Domande sui Signal nei Colloqui Angular
- I signals sono primitive sincrone; RxJS gestisce gli stream async
- I computed signals sono lazy e memoizzati, ricalcolano solo quando le dipendenze cambiano
- Gli effects vengono eseguiti in modo asincrono durante la change detection, non immediatamente
- linkedSignal risolve il problema dello stato derivato scrivibile senza effects
- I signal input (
input(),model()) sostituiscono i decoratori con alternative reattive toSignaletoObservablecollegano signals e RxJS per la migrazione incrementale- Angular zoneless si basa sui signals per la change detection fine-grained
- La Resource API (
rxResource) gestisce lo stato async in modo dichiarativo - La guida ufficiale Angular sui signals rimane il riferimento autorevole per i dettagli dell'API e le best practice
Sapresti trovare il bug in Angular?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 17 settembre 2026
Tag
Condividi
Articoli correlati

Domande di colloquio Angular 19: Signals, SSR e concetti imprescindibili
Le domande di colloquio Angular 19 più frequenti: Signals, idratazione incrementale, change detection senza Zone.js e nuove API reattive con esempi di codice e risposte attese.

Dependency Injection Avanzata in Angular 2026: Provider, Token e Domande da Colloquio
Il sistema di dependency injection di Angular offre un controllo preciso sull'istanziazione dei servizi attraverso provider, token e iniettori gerarchici. Questa analisi approfondita esplora le strategie dei provider, la funzione inject() e le domande tecniche da colloquio.

Angular Control Flow Syntax 2026: @if, @for, @switch e Domande da Colloquio
Guida completa alla nuova sintassi Control Flow di Angular con @if, @for e @switch. Ottimizzazioni delle performance, integrazione con i Signal e domande frequenti nei colloqui tecnici.