Skip to content

Praktische Use Cases ​

Door voorwaardelijke operators van RxJS te gebruiken, wordt het mogelijk om stream-vertakkingen en schakelingen uit te voeren op basis van dynamische status. In dit hoofdstuk kunt u de toepassingspatronen van elke operator ervaren via daadwerkelijk werkende code met UI.

Selectie van Verschillende Gegevensbronnen op Basis van Voorwaarden ​

ts
import { iif, of, EMPTY } from 'rxjs';
import { switchMap, tap, catchError, retry } from 'rxjs';

// UI aanmaken
const appContainer = document.createElement('div');
appContainer.innerHTML = '<h3>Gegevensbron selectie app:</h3>';
document.body.appendChild(appContainer);

// Optie selectie
const optionsDiv = document.createElement('div');
optionsDiv.style.marginBottom = '15px';
appContainer.appendChild(optionsDiv);

// Checkbox (offline modus)
const offlineCheck = document.createElement('input');
offlineCheck.type = 'checkbox';
offlineCheck.id = 'offlineMode';
optionsDiv.appendChild(offlineCheck);

const offlineLabel = document.createElement('label');
offlineLabel.htmlFor = 'offlineMode';
offlineLabel.textContent = 'Offline modus';
offlineLabel.style.marginLeft = '5px';
optionsDiv.appendChild(offlineLabel);

// Zoek-ID invoer
const idInput = document.createElement('input');
idInput.type = 'number';
idInput.placeholder = 'ID (1-10)';
idInput.min = '1';
idInput.max = '10';
idInput.value = '1';
idInput.style.marginLeft = '15px';
idInput.style.width = '80px';
optionsDiv.appendChild(idInput);

// Zoekknop
const searchButton = document.createElement('button');
searchButton.textContent = 'Zoeken';
searchButton.style.marginLeft = '10px';
optionsDiv.appendChild(searchButton);

// Resultatengebied
const resultsArea = document.createElement('div');
resultsArea.style.padding = '15px';
resultsArea.style.border = '1px solid #ddd';
resultsArea.style.borderRadius = '5px';
resultsArea.style.backgroundColor = '#f9f9f9';
resultsArea.style.minHeight = '150px';
appContainer.appendChild(resultsArea);

type User = {
  lastUpdated?: Date;
  fromCache?: boolean;
  id: number;
  name: string;
  email: string;
};
type ErrorResult = {
  error: boolean;
  message: string;
};

// Offline gegevens (cache)
const cachedData: Record<number, User> = {
  1: { id: 1, name: 'Jan Jansen', email: 'jan@example.com' },
  2: { id: 2, name: 'Pieter Pietersen', email: 'pieter@example.com' },
  3: { id: 3, name: 'Klaas Klaassen', email: 'klaas@example.com' },
};

// Daadwerkelijk gegevens ophalen van online API (JSONPlaceholder)
function fetchUserFromApi(id: number) {
  console.log(`Gebruiker ID ${id} ophalen van API...`);

  // Daadwerkelijk API endpoint
  const apiUrl = `https://jsonplaceholder.typicode.com/users/${id}`;

  return of(null).pipe(
    switchMap(() =>
      fetch(apiUrl).then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP fout: ${response.status}`);
        }
        return response.json();
      })
    ),
    tap(() => console.log('API aanroep geslaagd')),
    catchError((err) => {
      console.error('API aanroep mislukt:', err);
      throw new Error('API verzoek mislukt');
    })
  );
}

// Gebruiker ophalen uit cache
function getUserFromCache(id: number) {
  console.log(`Gebruiker ID ${id} ophalen uit cache...`);

  return iif(
    () => id in cachedData,
    of({ ...cachedData[id], fromCache: true }),
    EMPTY.pipe(
      tap(() => {
        throw new Error('Gebruiker niet gevonden in cache');
      })
    )
  );
}

// Zoekknop klik
searchButton.addEventListener('click', () => {
  const id = parseInt(idInput.value, 10);
  const isOffline = offlineCheck.checked;

  // Invoer validatie
  if (isNaN(id) || id < 1 || id > 10) {
    resultsArea.innerHTML =
      '<p style="color: red;">Voer een geldige ID (1-10) in</p>';
    return;
  }

  // Laadweergave
  resultsArea.innerHTML = '<p>Gegevens ophalen...</p>';

  // Gegevensbron selecteren op basis van offline modus
  iif(
    () => isOffline,
    getUserFromCache(id).pipe(
      catchError((err) => {
        console.error('Cache fout:', err);
        return of({ error: err.message });
      })
    ),
    fetchUserFromApi(id).pipe(
      retry(2), // Maximaal 2 keer opnieuw proberen
      catchError((err) => {
        console.error('API fout:', err);

        // Als API mislukt, gebruik cache als fallback
        return getUserFromCache(id).pipe(
          catchError(() =>
            of({ error: 'Zowel online API als cache mislukt' })
          )
        );
      })
    )
  ).subscribe({
    next: (result: any) => {
      if ('error' in result) {
        resultsArea.innerHTML = `<p style="color: red;">Fout: ${result.message}</p>`;
      } else {
        const source = result.fromCache
          ? '<span style="color: orange;">(van cache)</span>'
          : '<span style="color: green;">(van API)</span>';

        resultsArea.innerHTML = `
          <h4>Gebruikersinformatie ${source}</h4>
          <p><strong>ID:</strong> ${result.id}</p>
          <p><strong>Naam:</strong> ${result.name}</p>
          <p><strong>E-mail:</strong> ${result.email}</p>
          ${
            result.lastUpdated
              ? `<p><small>Laatst bijgewerkt: ${new Date(
                  result.lastUpdated
                ).toLocaleString()}</small></p>`
              : ''
          }
        `;
      }
    },
    error: (err) => {
      resultsArea.innerHTML = `<p style="color: red;">Fout: ${err.message}</p>`;
    },
  });
});

// Initieel bericht
resultsArea.innerHTML = '<p>Klik op de knop om gegevens op te halen</p>';

Runtime Vertakking en Fallback Strategieën ​

In dit voorbeeld met iif schakelt u dynamisch tussen gegevensbronnen "offline cache" en "online API" op basis van gebruikersacties of status. Door catchError en retry te combineren, kunt u ook retry's bij mislukkingen en fallback-bestemmingen definiëren.

Het is vooral geschikt voor de volgende use cases.

  • Offline ondersteuning in omgevingen met onstabiel netwerk
  • Schakelen tussen cache-gebruik en online verzoeken
  • Automatische retry's bij API-fouten en overschakelen naar alternatieve routes

Performance Optimalisatie Patronen ​

In complexere scenario's kunt u geoptimaliseerde gegevensophalings patronen implementeren door voorwaardelijke operators te combineren.

ts
import { fromEvent, Observable, of, throwError, timer } from 'rxjs';
import {
  switchMap,
  catchError,
  map,
  tap,
  debounceTime,
  distinctUntilChanged,
  withLatestFrom,
  delay,
  startWith,
} from 'rxjs';

// UI-elementen aanmaken
const optimizationContainer = document.createElement('div');
optimizationContainer.innerHTML = '<h3>Geavanceerde voorwaardelijke gegevensophaling:</h3>';
document.body.appendChild(optimizationContainer);

// Zoek UI
const searchInputGroup = document.createElement('div');
searchInputGroup.style.marginBottom = '15px';
optimizationContainer.appendChild(searchInputGroup);

const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Voer gebruiker ID in (1-10)';
searchInput.value = '1';
searchInput.style.padding = '8px';
searchInput.style.width = '180px';
searchInputGroup.appendChild(searchInput);

const searchButton = document.createElement('button');
searchButton.textContent = 'Zoeken';
searchButton.style.marginLeft = '10px';
searchButton.style.padding = '8px 16px';
searchInputGroup.appendChild(searchButton);

// Opties instellingen
const optionsGroup = document.createElement('div');
optionsGroup.style.marginBottom = '15px';
optimizationContainer.appendChild(optionsGroup);

const cacheCheckbox = document.createElement('input');
cacheCheckbox.type = 'checkbox';
cacheCheckbox.id = 'useCache';
cacheCheckbox.checked = true;
optionsGroup.appendChild(cacheCheckbox);

const cacheLabel = document.createElement('label');
cacheLabel.htmlFor = 'useCache';
cacheLabel.textContent = 'Cache gebruiken';
cacheLabel.style.marginRight = '15px';
optionsGroup.appendChild(cacheLabel);

const forceCheckbox = document.createElement('input');
forceCheckbox.type = 'checkbox';
forceCheckbox.id = 'forceRefresh';
optionsGroup.appendChild(forceCheckbox);

const forceLabel = document.createElement('label');
forceLabel.htmlFor = 'forceRefresh';
forceLabel.textContent = 'Geforceerd opnieuw ophalen';
optionsGroup.appendChild(forceLabel);

// Resultaten weergavegebied
const optimizedResults = document.createElement('div');
optimizedResults.style.padding = '15px';
optimizedResults.style.border = '1px solid #ddd';
optimizedResults.style.borderRadius = '5px';
optimizedResults.style.minHeight = '150px';
optimizedResults.style.backgroundColor = '#f9f9f9';
optimizationContainer.appendChild(optimizedResults);

// Cache beheer
const cache = new Map<string, { data: any; timestamp: number }>();
const CACHE_EXPIRY = 30000; // 30 seconden

// Gebruikersgegevens ophalen van daadwerkelijke API (JSONPlaceholder)
function fetchUserData(id: string, forceRefresh: boolean): Observable<any> {
  // Ongeldig ID
  if (!id || isNaN(Number(id)) || Number(id) < 1 || Number(id) > 10) {
    return throwError(
      () => new Error('Ongeldig gebruikers-ID: voer een getal van 1~10 in')
    );
  }

  const cacheKey = `user-${id}`;
  const cachedItem = cache.get(cacheKey);
  const now = Date.now();

  // Cache controle (binnen termijn en niet geforceerd opnieuw ophalen)
  if (
    !forceRefresh &&
    cachedItem &&
    now - cachedItem.timestamp < CACHE_EXPIRY
  ) {
    console.log(`Ophalen uit cache: ${id}`);
    return of({
      ...cachedItem.data,
      fromCache: true,
    }).pipe(delay(100)); // Simuleer snelle respons
  }

  // Daadwerkelijk API verzoek (JSONPlaceholder)
  console.log(`Gegevens ophalen van API: ${id}`);
  const apiUrl = `https://jsonplaceholder.typicode.com/users/${id}`;

  return of(null).pipe(
    switchMap(() =>
      fetch(apiUrl).then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP fout: ${response.status}`);
        }
        return response.json();
      })
    ),
    map((userData) => {
      const processedData = {
        id: userData.id,
        name: userData.name,
        email: userData.email,
        lastUpdated: now,
        fromCache: false,
      };

      // Opslaan in cache
      cache.set(cacheKey, {
        data: processedData,
        timestamp: now,
      });

      return processedData;
    }),
    catchError((err) => {
      console.error('API fout:', err);
      throw new Error('API verzoek mislukt');
    })
  );
}

// Zoekvoorwaarde wijzigingen monitoren
const searchTerm$ = fromEvent(searchInput, 'input').pipe(
  map((event) => (event.target as HTMLInputElement).value.trim()),
  debounceTime(300),
  distinctUntilChanged()
);

// Cache instellingen wijzigingen monitoren
const useCache$ = fromEvent(cacheCheckbox, 'change').pipe(
  map((event) => (event.target as HTMLInputElement).checked),
  startWith(true)
);

// Geforceerd opnieuw ophalen wijzigingen monitoren
const forceRefresh$ = fromEvent(forceCheckbox, 'change').pipe(
  map((event) => (event.target as HTMLInputElement).checked),
  startWith(false)
);

// Zoekknop klik event
const searchClick$ = fromEvent(searchButton, 'click');

// Zoeken uitvoeren
searchClick$
  .pipe(
    // Huidige invoerwaarde, cache instelling, geforceerd opnieuw ophalen instelling ophalen
    withLatestFrom(
      searchTerm$,
      useCache$,
      forceRefresh$,
      (_, term, useCache, forceRefresh) => ({
        term,
        useCache,
        forceRefresh,
      })
    ),
    tap(() => {
      // Zoeken starten weergave
      optimizedResults.innerHTML = '<p>Zoeken...</p>';
    }),
    // Voorwaardelijke stream met iif()
    switchMap(({ term, useCache, forceRefresh }) => {
      // Als zoekterm leeg is
      if (!term) {
        return of({ error: 'Voer een zoekterm in' });
      }

      // Als cache is uitgeschakeld
      if (!useCache) {
        return fetchUserData(term, true);
      }

      // Normale zoekopdracht (cache gebruiken & geforceerd opnieuw ophalen indien nodig)
      return fetchUserData(term, forceRefresh);
    }),
    // Foutafhandeling
    catchError((err) => {
      return of({ error: err.message });
    })
  )
  .subscribe({
    next: (result) => {
      if ('error' in result) {
        // Fout weergeven
        optimizedResults.innerHTML = `
        <p style="color: red;">Fout: ${result.error}</p>
      `;
      } else {
        // Gegevens weergeven
        const source = result.fromCache
          ? '<span style="color: orange;">(van cache)</span>'
          : '<span style="color: green;">(van API)</span>';

        optimizedResults.innerHTML = `
        <h4>Gebruikersinformatie ${source}</h4>
        <p><strong>ID:</strong> ${result.id}</p>
        <p><strong>Naam:</strong> ${result.name}</p>
        <p><strong>E-mail:</strong> ${result.email}</p>
        ${
          result.lastUpdated
            ? `<p><small>Laatst bijgewerkt: ${new Date(
                result.lastUpdated
              ).toLocaleString()}</small></p>`
            : ''
        }
      `;
      }
    },
  });

// Initieel bericht
optimizedResults.innerHTML =
  '<p>Voer gebruiker ID in en klik op de zoekknop</p>';

Operator Selectie Gids ​

Voorwaardelijke operators lijken vaak op elkaar en kunnen verwarrend zijn, maar elk heeft een duidelijk toepassingsdoel. Hieronder volgt een typische beslissingsstroom en vergelijking van kenmerken.

Hoe Voorwaardelijke Operators te Kiezen ​

OperatorUse CaseKenmerken
iifSelecteer één stream tijdens runtimeKies één uit twee opties op basis van voorwaarde
partitionVerdeel stream in twee op basis van voorwaardeVerdeel bronstream in True/False op voorwaarde
throwIfEmptyDetecteer lege streamGooi fout als geen enkele waarde wordt uitgezonden
defaultIfEmptyGebruik standaardwaarde als leegBied fallback-waarde wanneer stream leeg is

Selectie Beslissingsstroom ​

  1. Zijn er twee opties?

    • Ja → Gebruik iif
    • Nee → Ga verder
  2. Wilt u een stream splitsen?

    • Ja → Gebruik partition
    • Nee → Ga verder
  3. Wilt u omgaan met lege stream?

    • Ja → Wilt u lege stream als fout behandelen?
      • Ja → throwIfEmpty
      • Nee → defaultIfEmpty
    • Nee → Ga verder
  4. Wilt u waarden filteren op basis van voorwaarde?

    • Ja → Gebruik filter operator (basis filter operator)
    • Nee → Heroverweeg doel

Samenvatting ​

Voorwaardelijke operators zijn krachtige tools voor het besturen van stream flows en het vertakken van verwerking op basis van specifieke voorwaarden. De belangrijkste punten zijn als volgt:

  1. Reactieve flows op basis van beslissingen: Door voorwaardelijke operators te gebruiken, kunt u verwerking dynamisch wijzigen op basis van event- of gegevensstatus.
  2. Verbeterde foutafhandeling: Voorwaardelijke operators fungeren als een belangrijk onderdeel van foutafhandelingsstrategieën en maken graceful reacties op uitzonderingscases mogelijk.
  3. Optimalisatie kansen: Door voorwaardelijke uitvoering kunt u onnodige verwerking vermijden en vooral kostbare operaties zoals netwerk verzoeken en hardwaretoegang optimaliseren.
  4. Complexe applicatie flows: Door meerdere voorwaardelijke operators te combineren, kunt u complexe bedrijfslogica en statusbeheer declaratief uitdrukken.

Voorwaardelijke operators zijn bijzonder waardevol bij het implementeren van foutafhandeling, cache-strategieën, fallback-mechanismen en voorwaardelijke uitvoeringspatronen met RxJS. Door ze te combineren met andere operators, kunt u complexe applicatie flows bouwen op een declaratieve en type-veilige manier.

Uitgebracht onder de CC-BY-4.0 licentie.