Skip to content

window - Ventana por Evento ​

El operador window agrupa los valores del Observable fuente hasta que otro Observable emite un valor, y emite ese grupo como un nuevo Observable. Mientras que buffer devuelve un array, window devuelve un Observable<T>, lo que permite aplicar operadores adicionales a cada ventana.

🔰 Sintaxis básica y uso ​

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

// Emitir valores cada 100ms
const source$ = interval(100);

// Usar evento de clic como trigger
const clicks$ = fromEvent(document, 'click');

source$.pipe(
  window(clicks$),
  mergeAll() // Aplanar cada ventana
).subscribe(value => {
  console.log('Valor en ventana:', value);
});

// Se inicia una nueva ventana cada vez que se hace clic
  • Cada vez que clicks$ emite un valor, se crea una nueva ventana (Observable).
  • Cada ventana puede procesarse como un Observable independiente.

🌐 Documentación oficial de RxJS - window

💡 Patrones de uso típicos ​

  • División de streams basada en eventos
  • Aplicar diferentes procesos a cada ventana
  • Agrupar datos con delimitadores dinámicos
  • Procesamiento de agregación para cada ventana

🔍 Diferencia con buffer ​

OperadorSalidaCaso de uso
bufferArray (T[])Procesar valores agrupados juntos
windowObservable<T>Procesamiento de stream diferente por grupo
ts
import { interval, timer } from 'rxjs';
import { buffer, window, mergeAll } from 'rxjs';

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

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

// window - emite como Observable
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  console.log('Ventana (Observable):', window$);
  window$.subscribe(value => {
    console.log('  Valor en ventana:', value);
  });
});

🧠 Ejemplo de código práctico 1: Contar por ventana ​

Ejemplo de contar el número de eventos hasta el clic del botón.

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

// Crear botón
const button = document.createElement('button');
button.textContent = 'Delimitar ventana';
document.body.appendChild(button);

// Área de salida
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);

// Emitir valores cada 100ms
const source$ = interval(100);

// Usar clic del botón como trigger
const clicks$ = fromEvent(button, 'click');

let windowCount = 0;

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

    // Contar valores en cada ventana
    return window$.pipe(
      scan((count) => count + 1, 0)
    );
  }),
  mergeAll()
).subscribe(count => {
  output.textContent = `Ventana actual: ${windowCount}, Conteo: ${count}`;
});
  • Se crea una nueva ventana cada vez que se hace clic en el botón.
  • El número de valores en cada ventana se cuenta en tiempo real.

🎯 Ejemplo de código práctico 2: Aplicar diferentes procesos a cada ventana ​

Ejemplo avanzado que aplica diferentes procesos a cada ventana.

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) {
      // Ventana par: obtener solo los primeros 3
      console.log(`Ventana ${current}: obtener los primeros 3`);
      return window$.pipe(take(3));
    } else {
      // Ventana impar: obtener todos
      console.log(`Ventana ${current}: obtener todos`);
      return window$;
    }
  }),
  mergeAll()
).subscribe(value => {
  console.log(`Valor: ${value} (Ventana ${windowNumber})`);
});
  • Se pueden aplicar diferentes procesos mediante bifurcación condicional por ventana.
  • Como cada ventana es un Observable independiente, se pueden combinar operadores libremente.

🎯 Ejemplo de uso: Control con múltiples triggers ​

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

const source$ = interval(100);

// Múltiples triggers: clic o 3 segundos transcurridos
const clicks$ = fromEvent(document, 'click');
const threeSeconds$ = timer(3000, 3000);
const trigger$ = merge(clicks$, threeSeconds$);

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

    // Calcular valor total de cada ventana
    return window$.pipe(
      scan((sum, value) => sum + value, 0)
    );
  }),
  mergeAll()
).subscribe(sum => {
  console.log('Total actual:', sum);
});

⚠️ Puntos de atención ​

1. Gestión de suscripciones de ventanas ​

Como cada ventana es un Observable independiente, es necesario suscribirse explícitamente.

ts
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  // Si no se suscribe a la ventana misma, no fluyen los valores
  window$.subscribe(value => {
    console.log('Valor:', value);
  });
});

O usar mergeAll(), concatAll(), switchAll(), etc. para aplanar.

ts
source$.pipe(
  window(trigger$),
  mergeAll() // Fusionar todas las ventanas
).subscribe(value => {
  console.log('Valor:', value);
});

2. Atención a fugas de memoria ​

Problema: Si el Observable trigger no emite valores, la primera ventana permanecerá abierta para siempre y los valores se acumularán infinitamente.

❌ Mal ejemplo: El trigger no ocurre ​

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

const source$ = interval(100); // Continúa emitiendo valores cada 100ms

// El botón no existe o el usuario no hace clic
const button = document.querySelector('#start-button'); // Posibilidad de null
const clicks$ = fromEvent(button, 'click'); // Error o nunca se dispara

source$.pipe(
  window(clicks$), // Si clicks$ no se dispara, la primera ventana no se cierra
  mergeAll()
).subscribe();

// Problemas:
// - Si clicks$ no emite valores, la primera ventana permanece abierta
// - Los valores de source$ (0, 1, 2, 3...) se acumulan en memoria
// - Causa de fuga de memoria

✅ Buen ejemplo 1: Configurar timeout ​

Configurar un timeout para que la primera ventana no permanezca abierta demasiado tiempo.

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 a observable ficticio si button es null

// Cerrar ventana con lo que ocurra primero: clic o 5 segundos transcurridos
const autoClose$ = timer(5000); // Emitir valor automáticamente después de 5 segundos
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // La ventana se cierra definitivamente dentro de 5 segundos
  mergeAll()
).subscribe();

✅ Buen ejemplo 2: Cerrar ventanas periódicamente ​

Cerrar ventanas periódicamente e iniciar nuevas ventanas incluso sin clics.

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');

// Cerrar ventana con clics o cada 3 segundos
const autoClose$ = timer(3000, 3000); // Después de los primeros 3 segundos, cada 3 segundos
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // La ventana se cierra cada 3 segundos incluso sin clics
  mergeAll()
).subscribe();

// Resultado:
// - Incluso si el usuario no hace clic, la ventana se cierra automáticamente cada 3 segundos
// - Previene la acumulación infinita de valores en memoria

3. Superposición de ventanas ​

Por defecto, las ventanas no se superponen (la siguiente comienza después de que se cierra la anterior). Si se necesita superposición, usar windowToggle o windowWhen.

🆚 Comparación de operadores de la familia window ​

OperadorTiming de delimitaciónCaso de uso
windowEmisión de otro ObservableDivisión basada en eventos
windowTimeTiempo fijoDivisión basada en tiempo
windowCountCantidad fijaDivisión basada en cantidad
windowToggleObservable de inicio/finControl dinámico de inicio/fin
windowWhenCondición de cierre dinámicaCondición de fin diferente por ventana

📚 Operadores relacionados ​

  • buffer - Agrupar valores como array (versión de array de window)
  • windowTime - División de ventana basada en tiempo
  • windowCount - División de ventana basada en cantidad
  • windowToggle - Control de ventana con Observable de inicio/fin
  • windowWhen - División de ventana con condición de cierre dinámica
  • groupBy - Agrupar Observable por clave

Resumen ​

El operador window es una herramienta poderosa que divide streams usando Observables externos como triggers y puede procesar cada grupo como un Observable independiente.

  • ✅ Puede aplicar diferentes procesos a cada ventana
  • ✅ Control flexible basado en eventos
  • ✅ Compatible con operaciones de stream avanzadas
  • ⚠️ Requiere gestión de suscripciones
  • ⚠️ Atención a fugas de memoria

Publicado bajo licencia CC-BY-4.0.