Skip to content

fromEvent() - Converti Eventi in Observable ​

fromEvent() è una Funzione di Creazione che converte sorgenti di eventi come eventi DOM e Node.js EventEmitter in stream Observable.

Panoramica ​

fromEvent() permette alle pipeline RxJS di gestire elaborazione asincrona basata su eventi. Registra automaticamente i listener di eventi quando sottoscritto e rimuove automaticamente i listener quando annullato, riducendo notevolmente il rischio di memory leak.

Firma:

typescript
function fromEvent<T>(
  target: any,
  eventName: string,
  options?: EventListenerOptions | ((...args: any[]) => T)
): Observable<T>

Documentazione Ufficiale: 📘 RxJS Ufficiale: fromEvent()

Uso Base ​

Questo è l'esempio più semplice di trattare eventi DOM come Observable.

typescript
import { fromEvent } from 'rxjs';

const clicks$ = fromEvent(document, 'click');

clicks$.subscribe(event => {
  console.log('Pulsante cliccato:', event);
});

// L'evento viene emesso ogni volta che clicchi

Caratteristiche Importanti ​

1. Registrazione e Rimozione Automatica dei Listener ​

fromEvent() registra un listener di eventi alla subscription e rimuove automaticamente il listener all'annullamento.

typescript
import { fromEvent } from 'rxjs';

const clicks$ = fromEvent<MouseEvent>(document, 'click');

const subscription = clicks$.subscribe(event => {
  console.log('Posizione click:', event.clientX, event.clientY);
});

// Annulla subscription dopo 5 secondi (il listener eventi viene rimosso automaticamente)
setTimeout(() => {
  subscription.unsubscribe();
  console.log('Subscription annullata');
}, 5000);

IMPORTANT

Prevenzione Memory Leak

Quando viene chiamato unsubscribe(), removeEventListener() viene eseguito automaticamente internamente. Questo elimina la necessità di rimuovere manualmente i listener e riduce notevolmente il rischio di memory leak.

2. Cold Observable (Ogni Subscription Registra Listener Indipendente) ​

L'Observable creato da fromEvent() è un Cold Observable. Ogni subscription registra un listener di eventi indipendente.

typescript
import { fromEvent } from 'rxjs';

const clicks$ = fromEvent(document, 'click');

// Subscription 1 - Registra listener A
clicks$.subscribe(() => console.log('Observer 1: Click'));

// Aggiungi subscription 2 dopo 1 secondo - Registra listener B indipendentemente
setTimeout(() => {
  clicks$.subscribe(() => console.log('Observer 2: Click'));
}, 1000);

// Entrambi i listener si attivano su un singolo click
// Questo prova che ogni subscription ha un listener indipendente

NOTE

Prova Cold Observable

Un nuovo listener di eventi viene registrato ogni volta che ti sottoscrivi e rimosso quando annulli. Questa è una caratteristica dei Cold Observable. Tuttavia, poiché la sorgente eventi (es., elemento DOM) è esterna e condivisa, ha anche la proprietà Hot di "non ricevere eventi prima della subscription".

3. Supporto Tipi TypeScript ​

I tipi di evento possono essere specificati esplicitamente.

typescript
import { fromEvent } from 'rxjs';

const input = document.createElement('input');
input.type = 'text';
document.body.appendChild(input);
const input$ = fromEvent<InputEvent>(input, 'input');

input$.subscribe(event => {
  // Il tipo di event è InputEvent
  const target = event.target as HTMLInputElement;
  console.log('Valore input:', target.value);
});

4. Cold Observable ​

fromEvent() è un Cold Observable. Ogni subscription avvia un'esecuzione indipendente.

typescript
import { fromEvent } from 'rxjs';

const button = document.createElement('button');
button.innerText = "Subscribe";
document.body.appendChild(button);

const clicks$ = fromEvent(document, 'click');

// Prima subscription - viene aggiunto listener eventi
clicks$.subscribe(() => console.log('Subscriber A'));

// Seconda subscription - viene aggiunto un altro listener eventi
clicks$.subscribe(() => console.log('Subscriber B'));

// Entrambi i listener si attivano quando clicchi una volta
// Output:
// Subscriber A
// Subscriber B

NOTE

Caratteristiche Cold Observable:

  • Un'esecuzione indipendente viene avviata per ogni subscription
  • Ogni subscriber riceve il proprio stream di dati
  • Un listener eventi indipendente viene registrato per ogni subscription; unsubscribe rimuove automaticamente il listener

Vedi Cold Observable e Hot Observable per maggiori informazioni.

Casi d'Uso Pratici ​

1. Elaborazione Eventi Click ​

Controlla i click sui pulsanti e previeni click consecutivi.

typescript
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs';

const button = document.createElement('button');
button.innerText = "submit";
document.body.appendChild(button);

const clicks$ = fromEvent(button, 'click');

clicks$.pipe(
  debounceTime(300), // Ignora click consecutivi entro 300ms
  map(() => 'Invio in corso...')
).subscribe(message => {
  console.log(message);
  // Elaborazione chiamata API, ecc.
});

2. Validazione Input Form in Tempo Reale ​

Stream eventi input ed esegui validazione in tempo reale.

typescript
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs';

const label = document.createElement('label');
label.innerText = 'email: ';
const emailInput = document.createElement('input');
label.appendChild(emailInput);
document.body.appendChild(label);
const email$ = fromEvent<InputEvent>(emailInput, 'input');

email$.pipe(
  map(event => (event.target as HTMLInputElement).value),
  debounceTime(500), // Elabora 500ms dopo che l'input si ferma
  distinctUntilChanged() // Solo quando il valore cambia
).subscribe(email => {
  console.log('Target validazione:', email);
  // Elaborazione validazione email
  validateEmail(email);
});

function validateEmail(email: string): void {
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  console.log(isValid ? 'Indirizzo email valido' : 'Indirizzo email non valido');
}

3. Implementazione Drag & Drop ​

Combina eventi mouse per implementare drag & drop.

typescript
import { fromEvent } from 'rxjs';
import { switchMap, takeUntil, map } from 'rxjs';

// Crea elemento trascinabile
const element = document.createElement('div');
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = '#333';
element.style.position = 'absolute'; // Imposta posizionamento assoluto
element.style.left = '50px'; // Posizione iniziale
element.style.top = '50px';
element.style.cursor = 'move'; // Cursore trascinabile
document.body.appendChild(element);

const mousedown$ = fromEvent<MouseEvent>(element, 'mousedown');
const mousemove$ = fromEvent<MouseEvent>(document, 'mousemove');
const mouseup$ = fromEvent<MouseEvent>(document, 'mouseup');

mousedown$.pipe(
  switchMap(startEvent => {
    // Registra posizione click dentro l'elemento
    const startX = startEvent.clientX - element.offsetLeft;
    const startY = startEvent.clientY - element.offsetTop;

    return mousemove$.pipe(
      map(moveEvent => ({
        left: moveEvent.clientX - startX,
        top: moveEvent.clientY - startY
      })),
      takeUntil(mouseup$) // Termina al rilascio del mouse
    );
  })
).subscribe(({ left, top }) => {
  // Aggiorna posizione elemento
  element.style.left = `${left}px`;
  element.style.top = `${top}px`;
});

4. Monitoraggio Eventi Scroll ​

Usato per tracciare infinite scrolling e posizione scroll.

typescript
import { fromEvent } from 'rxjs';
import { throttleTime, map } from 'rxjs';

const scroll$ = fromEvent(window, 'scroll');

scroll$.pipe(
  throttleTime(200), // Elabora solo una volta ogni 200ms
  map(() => window.scrollY)
).subscribe(scrollPosition => {
  console.log('Posizione scroll:', scrollPosition);

  // Carica contenuto aggiuntivo quando raggiungi il fondo della pagina
  if (scrollPosition + window.innerHeight >= document.body.scrollHeight - 100) {
    console.log('Carica contenuto aggiuntivo');
    // loadMoreContent();
  }
});

Uso in Pipeline ​

fromEvent() è ideale per l'elaborazione pipeline che parte da stream di eventi.

typescript
import { fromEvent } from 'rxjs';
import { map, filter, scan } from 'rxjs';

const button = document.createElement('button');
button.innerText = "Counter";
document.body.appendChild(button);

const clicks$ = fromEvent(button, 'click');

clicks$.pipe(
  filter((event: Event) => {
    // Conta solo click mentre tieni premuto Shift
    return (event as MouseEvent).shiftKey;
  }),
  scan((count, _) => count + 1, 0),
  map(count => `Conteggio click: ${count}`)
).subscribe(message => console.log(message));

Errori Comuni ​

1. Dimenticare di Annullare Subscription ​

❌ Sbagliato - Dimenticare di annullare causa memory leak ​

typescript
import { fromEvent } from 'rxjs';

function setupEventListener() {
  const clicks$ = fromEvent(document, 'click');
  clicks$.subscribe(console.log); // Non annullato!
}

setupEventListener();

✅ Corretto - Annulla sempre subscription ​

typescript
import { fromEvent } from 'rxjs';
import { Subscription } from 'rxjs';

let subscription: Subscription;

function setupEventListener() {
  const clicks$ = fromEvent(document, 'click');
  subscription = clicks$.subscribe(console.log);
}

function cleanup() {
  if (subscription) {
    subscription.unsubscribe();
  }
}

setupEventListener();
// Chiama cleanup() quando il componente viene distrutto, ecc.

WARNING

Attenzione ai Memory Leak

Nelle SPA e nei framework basati su componenti, assicurati di annullare la subscription quando distruggi un componente. Se dimentichi di annullare, i listener eventi rimarranno e causeranno memory leak.

2. Registrazione Duplicata di Listener Eventi Multipli ​

❌ Sbagliato - Sottoscriversi allo stesso evento più volte registra più listener ​

typescript
import { fromEvent } from 'rxjs';

const clicks$ = fromEvent(document, 'click');

clicks$.subscribe(() => console.log('Observer 1'));
clicks$.subscribe(() => console.log('Observer 2'));
// Entrambi i log vengono visualizzati al click (due listener sono registrati)

✅ Corretto - Multicast con share() se necessario ​

typescript
import { fromEvent } from 'rxjs';
import { share } from 'rxjs';

const clicks$ = fromEvent(document, 'click').pipe(share());

clicks$.subscribe(() => console.log('Observer 1'));
clicks$.subscribe(() => console.log('Observer 2'));
// Un listener viene condiviso

Considerazioni sulle Performance ​

Le performance dovrebbero essere considerate quando si gestiscono eventi che si attivano ad alta frequenza (scroll, mousemove, resize, ecc.).

TIP

Ottimizzazione di Eventi ad Alta Frequenza:

  • throttleTime() - Elabora solo una volta per periodo di tempo
  • debounceTime() - Elabora dopo che l'input si ferma
  • distinctUntilChanged() - Elabora solo quando il valore cambia

❌ Problema di Performance - Elabora ad ogni resize ​

typescript
import { fromEvent } from 'rxjs';

const resize$ = fromEvent(window, 'resize');

resize$.subscribe(() => {
  console.log('Elaborazione resize'); // Elaborazione pesante
});

✅ Ottimizzazione - Elabora solo una volta ogni 200ms ​

typescript
import { fromEvent } from 'rxjs';
import { throttleTime } from 'rxjs';

const resize$ = fromEvent(window, 'resize');
resize$.pipe(
  throttleTime(200)
).subscribe(() => {
  console.log('Elaborazione resize'); // Riduzione carico
});

Funzioni di Creazione Correlate ​

FunzioneDifferenzaUso
from()Converti da array/PromiseStream dati diversi da eventi
interval()Emetti a intervalli regolariServe elaborazione periodica
fromEventPattern()Registrazione eventi personalizzataSistemi di eventi personalizzati diversi da EventEmitter
  • fromEvent() converte eventi DOM ed EventEmitter in Observable
  • Registra listener alla subscription, elimina automaticamente all'unsubscribe (previene memory leak)
  • Funziona come Hot Observable
  • Esegui sempre unsubscribe per prevenire memory leak
  • Ottimizza eventi ad alta frequenza con throttleTime() e debounceTime()

Prossimi Passi ​

Pubblicato sotto licenza CC-BY-4.0.