Di recente, è ripresa una discussione tra gli sviluppatori frontend, soprattutto quelli del mondo JavaScript, su una primitiva che presto verrà introdotta in alcuni dei più famosi framework come Angular e React: i segnali. Questo dibattito ha radici storiche e si concentra sull'utilizzo dei segnali come un modo per gestire gli eventi e lo stato delle applicazioni in modo più efficiente. Si sta quindi valutando se l'adozione dei segnali rappresenti l'aggiunta di una feature utile per gli sviluppatori, o l'ennesimo opt-in che lasciato in mani poco esperte può creare applicazioni difficili da scalare e piene di spaghetti code. Che gli sviluppatori lo vogliano o meno, i segnali sono in arrivo, e guadagnano sempre più popolarità grazie ad una sintassi chiara e semplice e alla predisposizione per la reattività .
La storia dei segnali
Nonostante la notorietà acquisita negli ultimi mesi, i segnali sono un concetto nato all'inizio dell'informatica, con i primi sistemi Unix che li utilizzano ancora per inviare notifiche tra processi in modo efficiente. I segnali hanno preso piede grazie alle prime ricerche svolte sulla programmazione reattiva, per poi essere implementati anche dagli amati framework dichiarativi JavaScript, spesso con nomi e logiche diverse, per permettere allo sviluppatore di gestire lo stato dell'applicazione e garantire un rilevamento delle modifiche e una renderizzazione puntuale, quindi efficente. Il pioniere dell'implementazione dei segnali in chiave moderna è sicuramente Solid.js, che utilizza i segnali per lo sviluppo di programmazione reattiva, consentendo l'utilizzo di reattività a grana fine (ovvero in dettaglio).
Ma cosa sono i segnali?
Solid.js definisce i segnali in questo modo:
"I segnali sono la pietra miliare della reattività in Solid. Contengono valori che cambiano nel tempo; quando modifichi il valore di un segnale, esso aggiorna automaticamente tutto ciò che lo utilizza."
I segnali sono un paradigma di programmazione ad eventi, simile a quello della DOM, che consente allo sviluppatore di generare eventi personalizzati e creare effetti nel codice all'emissione di un valore. L'utilizzo dei segnali offre numerosi vantaggi, tra cui la scrittura di codice semplice, reattivo e facile da mantenere. Inoltre, i segnali sono agnostici rispetto al framework utilizzato, il che significa che possono essere facilmente integrati a partire da una libreria JS vanilla.
Un segnale può essere semplicemente creato con un metodo, ad esempio signal
in Angular:
export class MyClass {
count = signal(0);
double = computed(() => this.count() * 2);
changeCount() {
this.count.set(5);
}
}
In questo esempio viene creato un segnale di tipo number con valore iniziale 0, e ne viene creato uno calcolato a partire dal primo. Nel momento in cui verrà eseguita la funzione changeCount
, il segnale count
verrà aggiornato con il valore 5, mentre double diventerà 10, grazie al metodo computed
che ritorna un segnale a partire da un altro.
Inoltre, vengono messi a disposizione dello sviluppatore anche altri metodi nel getter, utilizzati per la modifica e la notifica delle stesse agli osservatori, come mutate
, update
e effect
:
const count = signal(0);
count.set(2);
count.update(count => count + 1);
Il metodo update
permette di modificare il segnale a partire dal suo valore precedente, questo risulta utile nel caso di valori di partenza immutabili.
const ids = signal<Id[]>([]);
ids.mutate(id => {
id.push({name: 'Mario Rossi', dateOfBirth: '11/11/2000'});
});
Il metodo mutate
, analogamente all'update
permette di modificare il valore del segnale, aggiornandolo però direttamente.
const count = signal(0);
effect(() => console.log('Siamo arrivati a:', count()));
//LOG: Siamo arrivati a: 0
count.set(1);
//LOG: Siamo arrivati a: 1
Infine un segnale può generare effetti grazie alla funzione effect()
, quest'ultima procederà ad eseguire il codice al suo interno ogni volta che il segnale emetterà un valore.
Va specificato che l'implementazione mostrata è quella della preview di Angular versione 16, e in base al framework utilizzato, o a modifiche future, essa potrebbe variare drasticamente. Nel caso della nuova API dei segnali di Angular, verranno aggiunte delle helper functions per l'integrazione con RxJs, però non ancora definite.
Lo stato di Angular
La maggioranza della community di Angular è entusiasta dell'implementazione dei segnali, considerandoli un upgrade utile e necessario. Negli ultimi tre rilasci, il team di sviluppo ha introdotto numerose feature, come componenti standalone, miglioramento dello stack trace e l'aggiunta di direttive agli elementi host, che hanno modernizzato e semplificato il framework. Con queste migliorie, lo sviluppo risulta molto più facile e veloce, rendendo la curva di apprendimento meno ripida per i neofiti. Per comprendere appieno l'utilità dei segnali, tuttavia, è necessario capire in che modo Angular renderizza i componenti e come rileva le modifiche.
Il problema Zone.js e Change Detection
In Angular, il processo di Change Detection (rilevamento delle modifiche) è necessario per mantenere l'interfaccia sincronizzata con lo stato interno dell'applicazione. Esso consente al framework di aggiornare automaticamente la DOM al cambiamento dello stato dell'applicazione. Angular utilizza una libreria chiamata Zone.js per rilevare i cambiamenti. Questa libreria controlla gli oggetti del modello di dati dell'applicazione per determinare se i loro valori sono stati modificati. Tuttavia, l'uso di Zone.js può rappresentare un problema di performance per Angular, in quanto deve essere caricato ed eseguito ancora prima di poter rilevare modifiche sui dati. Inoltre, ogni evento deve essere tracciato anche se non provoca cambiamenti nel modello dei dati, e poiché Angular non utilizza una DOM virtuale, l'intero albero dei componenti viene controllato dal dirty checking, impattando la velocità e l'efficienza.
Come si può risolvere questo problema?
Attualmente, non è possibile risolvere definitivamente il problema generato dalla Change Detection in Angular. Tuttavia, gli sviluppatori utilizzano un'alternativa facilmente integrabile con la programmazione reattiva (su cui ti consiglio di leggere questo fantastico articolo scritto dai colleghi Cristian Bianco e Mauro Celani, che trovi qui). Questo metodo si basa sulla strategia OnPush, che consente di rilevare le modifiche solo al momento in cui vengono aggiornati i dati, ovvero al "push".
In particolare, utilizzano la pipe async
nei template, e quindi evitando di effettuare dei subscribe
nel codice, Angular invece di navigare l'albero dei componenti per intero, andrà a controllare solo il sottoalbero da cui è partita la modifica.
@Component({
selector: 'async-observable-pipe',
template: '<div><code>observable|async</code>: Time: {{ time | async }}</div>'
})
export class AsyncObservablePipeComponent {
time = new Observable<string>((observer: Observer<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});
}
La chiave per la reattività a grana fine
I problemi elencati saranno risolti dall'introduzione dei segnali in Angular. In particolare il team vuole introdurre una primitiva reattiva che integrata con il motore di templating di Angular permette di notificare il framework quando il valore associato alla vista cambia e deve essere aggiornato nella DOM. Questo processo di aggiornamento puntale della vista, anche chiamato reattività a grana fine, eliminerà la necessità di utilizzare Zone.js. Un ulteriore vantaggio dell'utilizzo dei segnali è la loro esecuzione senza glitch. Essi si basano su due astrazioni: il Producer e il Consumer. Viene costruito un albero delle dipendenze, dove ogni nodo ha due archi che lo collegano ai suoi nodi figlio o padre. Ciò consente a ogni nodo di conoscere lo stato dei suoi consumer e viceversa. In questo modo, un Producer può aggiornare tutti i suoi Consumer e propagare le modifiche senza emettere side-effect, evitando quindi di creare stati intermedi che lo sviluppatore dovrebbe gestire per evitare glitch. Quando tutti i nodi sono stati aggiornati (in modo sincrono), vengono eseguiti gli effetti, il che risolve un problema spesso incontrato nell'uso di Rxjs.
Il futuro di RxJS in Angular
Una delle domande più frequenti riguardo all'introduzione dei segnali in Angular è: RxJs verrà eliminato? La risposta è decisamente no. Nonostante i segnali siano uno strumento utile che migliorerà l'esperienza degli sviluppatori, essi non possono sostituire RxJs e i suoi observable in tutte le operazioni asincrone, come ad esempio le richieste http. La relazione tra i segnali e RxJs sarà probabilmente simbiotica, consentendo l'uso dei segnali per la gestione dei valori più immediati e l'utilizzo degli observable per tutte le operazioni asincrone che possono beneficiare dei suoi operatori avanzati. In questo modo, entrambi gli strumenti potranno coesistere e integrarsi efficacemente, migliorando l'efficienza e la scalabilità delle applicazioni Angular.
Conclusioni
Per concludere, l'introduzione dei segnali in Angular rappresenta una significativa evoluzione che migliorerà notevolmente l'esperienza degli sviluppatori e fornirà importanti incrementi di performance, consolidando ulteriormente la posizione di Angular come uno dei principali framework Javascript sul mercato. La combinazione dei segnali con RxJs, inoltre, offrirà una soluzione altamente efficiente e scalabile per la gestione di operazioni sincrone e asincrone, consentendo agli sviluppatori di creare applicazioni ancora più potenti e complesse.