Skip to content

window - Divide Observable no Momento de Outro Observable ​

O operador window agrupa os valores de um Observable fonte até que outro Observable emita valores e gera esse grupo como um novo Observable. Enquanto buffer retorna um array, window retorna um Observable<T>, permitindo que operadores adicionais sejam aplicados a cada janela.

🔰 Sintaxe Básica e Uso ​

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

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

// Usa evento de clique como gatilho
const clicks$ = fromEvent(document, 'click');

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

// Uma nova janela começa com cada clique
  • Cada vez que clicks$ emite um valor, uma nova janela (Observable) é criada.
  • Cada janela pode ser tratada como um Observable independente.

🌐 Documentação Oficial RxJS - window

💡 Padrões Típicos de Uso ​

  • Particionamento de stream orientado a eventos
  • Aplicar processamento diferente a cada janela
  • Agrupamento de dados com delimitação dinâmica
  • Processamento agregado para cada janela

🔍 Diferença de buffer ​

OperadorSaídaCaso de Uso
bufferArray (T[])Processar valores agrupados juntos
windowObservable<T>Processamento de stream diferente para cada grupo
ts
import { interval, timer } from 'rxjs';
import { buffer, window, mergeAll } from 'rxjs';

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

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

// window - Gera como Observable
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  console.log('Window (Observable):', window$);
  window$.subscribe(value => {
    console.log('  Valor na janela:', value);
  });
});

🧠 Exemplo de Código Prático 1: Contagem Por Janela ​

Este exemplo dispara no clique do botão e conta o número de eventos até esse ponto.

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

// Criar botão
const button = document.createElement('button');
button.textContent = 'Delimitar Janela';
document.body.appendChild(button);

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

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

// Gatilho ao clicar no botão
const clicks$ = fromEvent(button, 'click');

let windowCount = 0;

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

    // Conta valores em cada janela
    return window$.pipe(
      scan((count) => count + 1, 0)
    );
  }),
  mergeAll()
).subscribe(count => {
  output.textContent = `Janela atual: ${windowCount}, Contagem: ${count}`;
});
  • Cada vez que um botão é clicado, uma nova janela é criada.
  • O número de valores em cada janela é contado em tempo real.

🎯 Exemplo de Código Prático 2: Processamento Diferente Para Cada Janela ​

Este é um exemplo avançado que aplica processamento diferente a cada janela.

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) {
      // Janelas pares: Obter apenas os primeiros 3 itens
      console.log(`Janela ${current}: Obter primeiros 3 itens`);
      return window$.pipe(take(3));
    } else {
      // Janelas ímpares: Obter todos
      console.log(`Janela ${current}: Obter todos`);
      return window$;
    }
  }),
  mergeAll()
).subscribe(value => {
  console.log(`Valor: ${value} (Janela ${windowNumber})`);
});
  • Você pode aplicar condicionalmente processamento diferente para cada janela.
  • Cada janela é um Observable independente, então você pode combinar operadores livremente.

🎯 Exemplo Prático: Controle com Múltiplos Gatilhos ​

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

const source$ = interval(100);

// Múltiplos gatilhos: clique ou 3 segundos decorridos
const clicks$ = fromEvent(document, 'click');
const threeSeconds$ = timer(3000, 3000);
const trigger$ = merge(clicks$, threeSeconds$);

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

    // Calcula soma para cada janela
    return window$.pipe(
      scan((sum, value) => sum + value, 0)
    );
  }),
  mergeAll()
).subscribe(sum => {
  console.log('Soma atual:', sum);
});

⚠️ Notas ​

1. Gerenciamento de Inscrição de Janela ​

Cada janela é um Observable independente, então deve ser explicitamente inscrita.

ts
source$.pipe(
  window(trigger$)
).subscribe(window$ => {
  // Os valores não fluirão a menos que você se inscreva na própria janela
  window$.subscribe(value => {
    console.log('Valor:', value);
  });
});

Ou use mergeAll(), concatAll(), switchAll(), etc. para achatar.

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

2. Cuidado com Vazamentos de Memória ​

Problema: Se o Observable de gatilho não emitir valores, a primeira janela permanece aberta para sempre e os valores acumulam infinitamente.

❌ Exemplo Ruim: Gatilho Não Ocorre ​

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

const source$ = interval(100); // Continua a emitir valores a cada 100ms

// Botão não existe, ou usuário não clica
const button = document.querySelector('#start-button'); // Possivelmente null
const clicks$ = fromEvent(button, 'click'); // Erro ou nunca dispara

source$.pipe(
  window(clicks$), // Primeira janela não fecha se clicks$ não disparar
  mergeAll()
).subscribe();

// Problemas:
// - Se clicks$ não emitir, a primeira janela fica aberta
// - Valores de source$ (0, 1, 2, 3...) continuam a acumular na memória
// - Causa vazamento de memória

✅ Bom Exemplo 1: Definir Timeout ​

Defina um timeout para evitar que a primeira janela fique aberta por muito tempo.

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

// Fecha janela ao clicar ou após 5 segundos, o que vier primeiro
const autoClose$ = timer(5000); // Emite após 5 segundos
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // Janela sempre fechará dentro de 5 segundos
  mergeAll()
).subscribe();

✅ Bom Exemplo 2: Fechar Janelas Periodicamente ​

Feche janelas periodicamente mesmo sem cliques.

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

// Fecha janela ao clicar ou a cada 3 segundos
const autoClose$ = timer(3000, 3000); // Após primeiros 3 segundos, depois a cada 3 segundos
const trigger$ = merge(clicks$, autoClose$);

source$.pipe(
  window(trigger$), // Janela fecha a cada 3 segundos mesmo sem cliques
  mergeAll()
).subscribe();

// Resultado:
// - Janelas fecham automaticamente a cada 3 segundos mesmo sem cliques do usuário
// - Previne acúmulo infinito de valores na memória

3. Sobreposição de Janela ​

Por padrão, janelas não se sobrepõem (próxima janela começa após fechamento da anterior). Se sobreposição for necessária, use windowToggle ou windowWhen.

🆚 Comparação de Operadores window ​

OperadorMomento da DelimitaçãoCaso de Uso
windowOutro Observable emiteParticionamento orientado a eventos
windowTimeIntervalo de tempo fixoParticionamento baseado em tempo
windowCountContagem fixaParticionamento baseado em contagem
windowToggleObservables de início e fimControle dinâmico de início/fim
windowWhenCondição de fechamento dinâmicaCondição de fim diferente por janela

📚 Operadores Relacionados ​

  • buffer - Coleta valores como array (versão array de window)
  • windowTime - Particionamento de janela baseado em tempo
  • windowCount - Particionamento de janela baseado em contagem
  • windowToggle - Controle de janela com Observables de início e fim
  • windowWhen - Particionamento de janela com condição de fechamento dinâmica
  • groupBy - Agrupar Observables por chave

Resumo ​

O operador window é uma ferramenta poderosa que divide streams acionados por um Observable externo e pode processar cada grupo como um Observable independente.

  • ✅ Pode aplicar processamento diferente a cada janela
  • ✅ Controle flexível orientado a eventos
  • ✅ Suporta operações avançadas de stream
  • ⚠️ Gerenciamento de inscrição necessário
  • ⚠️ Cuidado com vazamentos de memória

Lançado sob a licença CC-BY-4.0.