Skip to content

window - Dividi Observable al Timing di un Altro Observable ​

L'operatore window raggruppa i valori di un Observable sorgente fino a quando un altro Observable emette valori ed emette quel gruppo come un nuovo Observable. Mentre buffer restituisce un array, window restituisce un Observable<T>, permettendo di applicare ulteriori operatori a ogni finestra.

🔰 Sintassi e Utilizzo Base ​

ts
import { interval, fromEvent } from 'rxjs';
import { window, mergeAll } from 'rxjs';

// Emetti valori ogni 100ms
const source$ = interval(100);

// Usa l'evento click come trigger
const clicks$ = fromEvent(document, 'click');

source$.pipe(
  window(clicks$),
  mergeAll() // Appiattisci ogni finestra
).subscribe(value => {
  console.log('Valore nella finestra:', value);
});

// Una nuova finestra inizia con ogni click
  • Ogni volta che clicks$ emette un valore, viene creata una nuova finestra (Observable).
  • Ogni finestra può essere trattata come un Observable indipendente.

🌐 Documentazione Ufficiale RxJS - window

💡 Pattern di Utilizzo Tipici ​

  • Partizionamento stream guidato da eventi
  • Applicare elaborazione diversa a ogni finestra
  • Raggruppamento dati con delimitazione dinamica
  • Elaborazione aggregata per ogni finestra

🔍 Differenza da buffer ​

OperatoreOutputCaso d'Uso
bufferArray (T[])Elabora insieme i valori raggruppati
windowObservable<T>Elaborazione stream diversa per ogni gruppo
ts
import { interval, timer } from 'rxjs';
import { buffer, window, mergeAll } from 'rxjs';

const source$ = interval(100);
const trigger$ = timer(1000, 1000);

// buffer - Output come array
source$.pipe(
  buffer(trigger$)
).subscribe(values => {
  console.log('Buffer (array):', values);
  // Output: Buffer (array): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
});

// window - Output come Observable
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  console.log('Window (Observable):', window$);
  window$.subscribe(value => {
    console.log('  Valore nella finestra:', value);
  });
});

🧠 Esempio di Codice Pratico 1: Conteggio per Finestra ​

Questo esempio attiva il click del bottone e conta il numero di eventi fino a quel punto.

ts
import { interval, fromEvent } from 'rxjs';
import { window, map, mergeAll, scan } from 'rxjs';

// Crea bottone
const button = document.createElement('button');
button.textContent = 'Delimita Finestra';
document.body.appendChild(button);

// Area di output
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);

// Emetti valori ogni 100ms
const source$ = interval(100);

// Trigger al click del bottone
const clicks$ = fromEvent(button, 'click');

let windowCount = 0;

source$.pipe(
  window(clicks$),
  map(window$ => {
    const currentWindow = ++windowCount;
    console.log(`Finestra ${currentWindow} iniziata`);

    // Conta i valori in ogni finestra
    return window$.pipe(
      scan((count) => count + 1, 0)
    );
  }),
  mergeAll()
).subscribe(count => {
  output.textContent = `Finestra corrente: ${windowCount}, Conteggio: ${count}`;
});
  • Ogni volta che si clicca un bottone, viene creata una nuova finestra.
  • Il numero di valori in ogni finestra viene contato in tempo reale.

🎯 Esempio di Codice Pratico 2: Elaborazione Diversa per Ogni Finestra ​

Questo è un esempio avanzato che applica elaborazione diversa a ogni finestra.

ts
import { interval, fromEvent } from 'rxjs';
import { window, take, mergeAll, map } from 'rxjs';

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

let windowNumber = 0;

source$.pipe(
  window(clicks$),
  map(window$ => {
    const current = ++windowNumber;

    if (current % 2 === 0) {
      // Finestre pari: Ottieni solo i primi 3 elementi
      console.log(`Finestra ${current}: Ottieni primi 3 elementi`);
      return window$.pipe(take(3));
    } else {
      // Finestre dispari: Ottieni tutti
      console.log(`Finestra ${current}: Ottieni tutti`);
      return window$;
    }
  }),
  mergeAll()
).subscribe(value => {
  console.log(`Valore: ${value} (Finestra ${windowNumber})`);
});
  • Puoi applicare condizionalmente elaborazione diversa per ogni finestra.
  • Ogni finestra è un Observable indipendente, quindi puoi combinare liberamente gli operatori.

🎯 Esempio Pratico: Controllo con Trigger Multipli ​

ts
import { interval, merge, fromEvent, timer } from 'rxjs';
import { window, mergeAll, scan, map } from 'rxjs';

const source$ = interval(100);

// Trigger multipli: click o 3 secondi trascorsi
const clicks$ = fromEvent(document, 'click');
const threeSeconds$ = timer(3000, 3000);
const trigger$ = merge(clicks$, threeSeconds$);

source$.pipe(
  window(trigger$),
  map((window$, index) => {
    console.log(`Finestra ${index + 1} iniziata`);

    // Calcola la somma per ogni finestra
    return window$.pipe(
      scan((sum, value) => sum + value, 0)
    );
  }),
  mergeAll()
).subscribe(sum => {
  console.log('Somma corrente:', sum);
});

⚠️ Note ​

1. Gestione Subscription delle Finestre ​

Ogni finestra è un Observable indipendente, quindi deve essere sottoscritto esplicitamente.

ts
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  // I valori non fluiranno a meno che non ti iscrivi alla finestra stessa
  window$.subscribe(value => {
    console.log('Valore:', value);
  });
});

Oppure usa mergeAll(), concatAll(), switchAll(), ecc. per appiattire.

ts
source$.pipe(
  window(trigger$),
  mergeAll() // Unisci tutte le finestre
).subscribe(value => {
  console.log('Valore:', value);
});

2. Attenzione ai Memory Leak ​

Problema: Se l'Observable trigger non emette valori, la prima finestra rimane aperta per sempre e i valori si accumulano all'infinito.

❌ Esempio Sbagliato: Il Trigger Non Si Verifica ​

ts
import { interval, fromEvent } from 'rxjs';
import { window, mergeAll } from 'rxjs';

const source$ = interval(100); // Continua a emettere valori ogni 100ms

// Il bottone non esiste, o l'utente non clicca
const button = document.querySelector('#start-button'); // Possibilmente null
const clicks$ = fromEvent(button, 'click'); // Errore o non si attiva mai

source$.pipe(
  window(clicks$), // La prima finestra non si chiuderà se clicks$ non si attiva
  mergeAll()
).subscribe();

// Problemi:
// - Se clicks$ non emette, la prima finestra rimane aperta
// - I valori di source$ (0, 1, 2, 3...) continuano ad accumularsi in memoria
// - Causa memory leak

✅ Esempio Corretto 1: Imposta Timeout ​

Imposta un timeout per evitare che la prima finestra rimanga aperta troppo a lungo.

ts
import { interval, fromEvent, timer, merge } from 'rxjs';
import { window, mergeAll } from 'rxjs';

const source$ = interval(100);
const button = document.querySelector('#start-button');
const clicks$ = button ? fromEvent(button, 'click') : interval(0); // fallback

// Chiudi finestra al click o dopo 5 secondi, qualunque venga prima
const autoClose$ = timer(5000); // Emetti dopo 5 secondi
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // La finestra si chiuderà sempre entro 5 secondi
  mergeAll()
).subscribe();

✅ Esempio Corretto 2: Chiudi Finestre Periodicamente ​

Chiudi le finestre periodicamente anche senza click.

ts
import { interval, fromEvent, timer, merge } from 'rxjs';
import { window, mergeAll } from 'rxjs';

const source$ = interval(100);
const button = document.querySelector('#start-button');
const clicks$ = fromEvent(button, 'click');

// Chiudi finestra al click o ogni 3 secondi
const autoClose$ = timer(3000, 3000); // Dopo i primi 3 secondi, poi ogni 3 secondi
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // La finestra si chiude ogni 3 secondi anche senza click
  mergeAll()
).subscribe();

// Risultato:
// - Le finestre si chiudono automaticamente ogni 3 secondi anche senza click dell'utente
// - Previene l'accumulo infinito di valori in memoria

3. Overlap delle Finestre ​

Per default, le finestre non si sovrappongono (la finestra successiva inizia dopo la chiusura della precedente). Se serve overlap, usa windowToggle o windowWhen.

🆚 Confronto degli Operatori window ​

OperatoreTiming del DelimitatoreCaso d'Uso
windowUn altro Observable emettePartizionamento guidato da eventi
windowTimeIntervallo di tempo fissoPartizionamento basato sul tempo
windowCountConteggio fissoPartizionamento basato sul conteggio
windowToggleObservable di inizio e fineControllo dinamico inizio/fine
windowWhenCondizione di chiusura dinamicaCondizione di fine diversa per finestra

📚 Operatori Correlati ​

  • buffer - Raccogli valori come array (versione array di window)
  • windowTime - Partizionamento finestre basato sul tempo
  • windowCount - Partizionamento finestre basato sul conteggio
  • windowToggle - Controllo finestre con Observable di inizio e fine
  • windowWhen - Partizionamento finestre con condizione di chiusura dinamica
  • groupBy - Raggruppa Observable per chiave

L'operatore window è uno strumento potente che divide gli stream attivati da un Observable esterno e può elaborare ogni gruppo come un Observable indipendente.

  • ✅ Può applicare elaborazione diversa a ogni finestra
  • ✅ Controllo flessibile guidato da eventi
  • ✅ Supporta operazioni stream avanzate
  • ⚠️ Richiede gestione subscription
  • ⚠️ Attenzione ai memory leak

Pubblicato sotto licenza CC-BY-4.0.