Skip to content

windowToggle - Venster met Trigger ​

De windowToggle operator controleert starttrigger en eindtrigger met aparte Observables, waarbij elke periode als een nieuwe Observable wordt uitgegeven. Dit is een geavanceerde vensteroperator die meerdere vensterperiodes tegelijk kan beheren.

🔰 Basissyntax en gebruik ​

ts
import { interval } from 'rxjs';
import { windowToggle, mergeAll } from 'rxjs';

const source$ = interval(500); // Geef waarden elke 0,5 seconden uit

// Starttrigger: elke 2 seconden
const opening$ = interval(2000);

// Eindtrigger: 1 seconde na start
const closing = () => interval(1000);

source$.pipe(
  windowToggle(opening$, closing),
  mergeAll()
).subscribe(value => {
  console.log('Waarde in venster:', value);
});

// Start op 2 sec, eindigt op 3 sec → Waarden: 4, 5
// Start op 4 sec, eindigt op 5 sec → Waarden: 8, 9
// Start op 6 sec, eindigt op 7 sec → Waarden: 12, 13

Werkingsstroom:

  1. opening$ zendt een waarde uit → Venster start
  2. Observable geretourneerd door closing() zendt een waarde uit → Venster eindigt
  3. Meerdere vensterperiodes kunnen overlappen

🌐 RxJS Officiële Documentatie - windowToggle

💡 Typische gebruikspatronen ​

  • Dataverzameling tijdens kantooruren
  • Gebeurtenisregistratie tijdens knop indrukken
  • Actietracking tijdens actieve sessies
  • Streamverwerking die dynamisch periodebeheer vereist

🔍 Verschil met bufferToggle ​

OperatorUitvoerGebruiksscenario
bufferToggleArray (T[])Gegroepeerde waarden samen verwerken
windowToggleObservable<T>Verschillende streamverwerking voor elke groep
ts
import { interval } from 'rxjs';
import { bufferToggle, windowToggle, mergeAll } from 'rxjs';

const source$ = interval(500);
const opening$ = interval(2000);
const closing = () => interval(1000);

// bufferToggle - Uitvoer als array
source$.pipe(
  bufferToggle(opening$, closing)
).subscribe(values => {
  console.log('Buffer (array):', values);
  // Uitvoer: Buffer (array): [4, 5]
});

// windowToggle - Uitvoer als Observable
source$.pipe(
  windowToggle(opening$, closing)
).subscribe(window$ => {
  console.log('Venster (Observable):', window$);
  window$.subscribe(value => {
    console.log('  Waarde in venster:', value);
  });
});

🧠 Praktisch codevoorbeeld 1: Gebeurtenissen registreren tijdens knop indrukken ​

Dit is een voorbeeld van het registreren van data tussen muis omlaag en muis omhoog.

ts
import { fromEvent, interval } from 'rxjs';
import { windowToggle, mergeMap, toArray, take } from 'rxjs';

// Maak knop
const button = document.createElement('button');
button.textContent = 'Vasthouden';
document.body.appendChild(button);

// Uitvoergebied
const display = document.createElement('div');
display.style.marginTop = '10px';
document.body.appendChild(display);

// Datastroom (elke 100ms)
const data$ = interval(100);

// Start: Muis omlaag
const mouseDown$ = fromEvent(button, 'mousedown');

// Einde: Muis omhoog
const mouseUp = () => fromEvent(document, 'mouseup').pipe(take(1));

data$.pipe(
  windowToggle(mouseDown$, mouseUp),
  mergeMap(window$ => window$.pipe(toArray()))
).subscribe(events => {
  display.textContent = `Gebeurtenissen geregistreerd tijdens vasthouden: ${events.length} items`;
  console.log('Geregistreerde data:', events);
});

🎯 Praktisch codevoorbeeld 2: Dataverzameling tijdens kantooruren ​

Dit is een voorbeeld van het verzamelen van sensordata van het begin van kantooruren tot het einde van kantooruren.

ts
import { interval, timer } from 'rxjs';
import { windowToggle, mergeMap, scan, map } from 'rxjs';

// Sensordata (altijd verzamelend)
const sensorData$ = interval(100).pipe(
  map(() => ({
    timestamp: Date.now(),
    temperature: 20 + Math.random() * 10, // 20-30 graden
    humidity: 40 + Math.random() * 20     // 40-60%
  }))
);

// Kantoor open: na 2 seconden, dan elke 10 seconden
const businessOpen$ = timer(2000, 10000);

// Kantoor sluit: 5 seconden na start
const businessClose = () => timer(5000);

let sessionNumber = 0;

sensorData$.pipe(
  windowToggle(businessOpen$, businessClose),
  mergeMap(window$ => {
    const current = ++sessionNumber;
    console.log(`Kantoorsessie ${current} gestart`);

    // Bereken statistieken voor elk venster
    return window$.pipe(
      scan((stats, data) => ({
        count: stats.count + 1,
        totalTemp: stats.totalTemp + data.temperature,
        totalHumidity: stats.totalHumidity + data.humidity
      }), { count: 0, totalTemp: 0, totalHumidity: 0 }),
      map(stats => ({
        session: current,
        count: stats.count,
        avgTemp: stats.totalTemp / stats.count,
        avgHumidity: stats.totalHumidity / stats.count
      }))
    );
  })
).subscribe(stats => {
  console.log(`Sessie ${stats.session}: ${stats.count} samples`);
  console.log(`  Gemiddelde temperatuur: ${stats.avgTemp.toFixed(1)}°C`);
  console.log(`  Gemiddelde vochtigheid: ${stats.avgHumidity.toFixed(1)}%`);
});

🎯 Praktisch voorbeeld: Downloadperiodebeheer ​

Dit is een voorbeeld van het beheren van datadownloadperiodes met start- en stopknoppen.

ts
import { interval, fromEvent, Subject } from 'rxjs';
import { windowToggle, mergeMap, toArray, map } from 'rxjs';

// Maak UI-elementen
const startButton = document.createElement('button');
startButton.textContent = 'Start';
document.body.appendChild(startButton);

const stopButton = document.createElement('button');
stopButton.textContent = 'Stop';
stopButton.disabled = true;
stopButton.style.marginLeft = '10px';
document.body.appendChild(stopButton);

const status = document.createElement('div');
status.style.marginTop = '10px';
status.textContent = 'Wachten...';
document.body.appendChild(status);

const result = document.createElement('div');
result.style.marginTop = '10px';
document.body.appendChild(result);

// Datastroom (genereer downloaddata elke 1 seconde)
const downloadData$ = interval(1000).pipe(
  map(i => ({
    id: i,
    size: Math.floor(Math.random() * 1000) + 100, // 100-1100KB
    timestamp: new Date()
  }))
);

// Start- en stoptriggers
const start$ = fromEvent(startButton, 'click');
const stop$ = new Subject<void>();

fromEvent(stopButton, 'click').subscribe(() => {
  stop$.next();
  status.textContent = 'Gestopt';
  startButton.disabled = false;
  stopButton.disabled = true;
});

start$.subscribe(() => {
  status.textContent = 'Downloaden...';
  startButton.disabled = true;
  stopButton.disabled = false;
});

// Vensterbeheer
downloadData$.pipe(
  windowToggle(start$, () => stop$),
  mergeMap(window$ => window$.pipe(toArray()))
).subscribe(downloads => {
  const totalSize = downloads.reduce((sum, d) => sum + d.size, 0);
  const avgSize = downloads.length > 0 ? totalSize / downloads.length : 0;

  result.innerHTML = `
    <strong>Download voltooid</strong><br>
    Aantal: ${downloads.length} items<br>
    Totale grootte: ${(totalSize / 1024).toFixed(2)} MB<br>
    Gemiddelde grootte: ${avgSize.toFixed(0)} KB
  `;
});

🎯 Overlappende vensterperiodes ​

Een kenmerk van windowToggle is dat het meerdere vensterperiodes tegelijk kan beheren.

ts
import { interval } from 'rxjs';
import { windowToggle, mergeMap, toArray, take } from 'rxjs';

const source$ = interval(200).pipe(take(20)); // 0-19

// Start: elke 1 seconde
const opening$ = interval(1000);

// Einde: 1,5 seconden na start
const closing = () => interval(1500);

source$.pipe(
  windowToggle(opening$, closing),
  mergeMap(window$ => window$.pipe(toArray()))
).subscribe(values => {
  console.log('Venster:', values);
});

// Uitvoer:
// Venster: [4, 5, 6, 7]       (Start op 1 sec → Eindigt op 2,5 sec)
// Venster: [9, 10, 11, 12]    (Start op 2 sec → Eindigt op 3,5 sec)
// Venster: [14, 15, 16, 17]   (Start op 3 sec → Eindigt op 4,5 sec)

Tijdlijn:

Bron:      0--1--2--3--4--5--6--7--8--9--10-11-12-13-14-15-16-17-18-19
Start:     ----1sec----2sec----3sec----4sec
Periode 1: [------1.5sec-----]
            └→ Venster 1: [4,5,6,7]
Periode 2:        [------1.5sec-----]
                   └→ Venster 2: [9,10,11,12]
Periode 3:               [------1.5sec-----]
                          └→ Venster 3: [14,15,16,17]

⚠️ Opmerkingen ​

1. Venster abonnementbeheer ​

Elk venster is een onafhankelijke Observable, dus u moet er expliciet op abonneren of het plat maken met mergeAll() of vergelijkbaar.

ts
source$.pipe(
  windowToggle(opening$, closing)
).subscribe(window$ => {
  // Waarden stromen niet tenzij u abonneert op het venster zelf
  window$.subscribe(value => {
    console.log('Waarde:', value);
  });
});

2. Pas op voor geheugenlekken ​

Als starttriggers te frequent zijn, zullen veel vensters tegelijk bestaan, wat geheugen verbruikt.

ts
// ❌ Slecht voorbeeld: Start elke 100ms, eindig na 5 seconden
const opening$ = interval(100); // Te frequent
const closing = () => interval(5000);

source$.pipe(
  windowToggle(opening$, closing)
).subscribe();
// Tot 50 vensters kunnen tegelijk bestaan → Geheugenrisico

// ✅ Goed voorbeeld: Stel passend interval in
const opening$ = interval(2000); // Elke 2 seconden
const closing = () => interval(1000); // Voor 1 seconde

3. Overlappende vensterperiodes ​

Overlappende vensterperiodes resulteren in dezelfde waarde in meerdere vensters. Controleer of dit het beoogde gedrag is.

ts
// Met overlap
opening$ = interval(1000);    // Start elke 1 seconde
closing = () => interval(1500); // Voor 1,5 seconden

// Zonder overlap
opening$ = interval(2000);    // Start elke 2 seconden
closing = () => interval(1000); // Voor 1 seconde

🆚 Vergelijking van window operators ​

OperatorControleVensterperiodeGebruiksscenario
windowAndere Observable zendt uitContinuEvent-gedreven partitionering
windowTimeVast tijdsintervalContinuTijd-gebaseerde partitionering
windowCountVast aantalContinuAantal-gebaseerde partitionering
windowToggleAparte start/eind controleKan overlappenComplexe start/eind condities
windowWhenAlleen eind controleContinuEenvoudige periodieke controle

🔄 Verschil met windowWhen ​

ts
import { interval, timer } from 'rxjs';
import { windowToggle, windowWhen, mergeAll } from 'rxjs';

const source$ = interval(200);

// windowToggle: Aparte controle van start en einde
source$.pipe(
  windowToggle(
    interval(1000),          // Starttrigger
    () => timer(500)         // Eindtrigger (500ms na start)
  ),
  mergeAll()
).subscribe();

// windowWhen: Controleer alleen eindtiming (volgende start onmiddellijk na einde)
source$.pipe(
  windowWhen(() => timer(1000)), // Venster elke 1 seconde
  mergeAll()
).subscribe();
OperatorControleVensterperiodeGebruiksscenario
windowToggle(open$, close)Aparte start/eind controleKan overlappenComplexe start/eind condities
windowWhen(closing)Alleen eind controleContinuEenvoudig periodiek venster

📚 Gerelateerde operators ​

  • bufferToggle - Verzamel waarden als array (array versie van windowToggle)
  • window - Splits venster op timing van verschillende Observable
  • windowTime - Tijd-gebaseerde venstersplitsing
  • windowCount - Aantal-gebaseerde venstersplitsing
  • windowWhen - Venstersplitsing met dynamische sluitcondities

Samenvatting ​

De windowToggle operator is een geavanceerd hulpmiddel waarmee u de start en het einde onafhankelijk kunt controleren en elke periode als een onafhankelijke Observable kunt behandelen.

  • ✅ Start en einde kunnen afzonderlijk worden gecontroleerd
  • ✅ Meerdere vensters kunnen tegelijk worden beheerd
  • ✅ Verschillende verwerking kan worden toegepast op elk venster
  • ⚠️ Abonnementbeheer vereist
  • ⚠️ Frequente starttriggers verbruiken geheugen
  • ⚠️ Wees bewust van overlappende vensterperiodes

Uitgebracht onder de CC-BY-4.0 licentie.