Subscription zu Query- und Route Parametern in Angular/RxJS

Beim Entwickeln von Angular-Anwendungen kann es erforderlich sein, sowohl Query- als auch Route-Parameter gleichzeitig auszulesen. Dies ist nicht ganz trivial, da in Angular Subscriptions für Query- und Route-Parameter unabhängig voneinander und asynchron ausgeführt werden. Dadurch kann es passieren, dass die Daten aus den Route-Parametern noch nicht geladen sind, während bereits auf die Query-Parameter zugegriffen wird. Dieser Artikel zeigt, wie man diese beiden Arten von Parametern synchron mit Hifle von RxJS auslesen kann.

Ein typisches Beispiel zeigt das Problem:

this.route.params.subscribe(params => {
    const { id } = params;
    this.foobar = this.loadFoobar(id);
});

this.route.queryParams.subscribe(query => {
    const { dingDong } = query;
    // this.foobar ist undefined !!!
    this.foobar.setDingDong(dingDong);
});

In diesem Beispiel wird das foobar-Objekt anhand der Route-Parameter geladen. Wenn jedoch die Query-Parameter abgerufen werden, ist foobar möglicherweise noch nicht initialisiert.

Lösung 1: Verwendung von setTimeout

Eine Möglichkeit, dieses Problem zu umgehen, ist die Verwendung von setTimeout, um sicherzustellen, dass das foobar-Objekt geladen ist, bevor man auf die Query-Parameter zugreift.

this.route.queryParams.subscribe(query => {
    if (query.bar) {
        // Referenz auf die Komponente für den Callback:
        // Dies is notwending, weil wir für den Fall dass "foobar" noch nicht da ist,
        // die Funktion "loadFoo" aus "setTimeout" aufrufen und sonst
        // die "this" Referenz verloren gehen würde
        const page = this;
        const loadFoo = () => {
            if (page.foobar) {
                // Hier habe ich sowohl "foobar" als auch die Query-Parameter
                page.foobar.setDingDong(query.dingDong);
            } else {
                // Hier ist foobar noch nicht geladen
                setTimeout(loadFoo, 50);
            }
        };
        loadFoo();
    }
});

In der Subscription für die Query-Parameter wird geprüft, ob das Objekt, das aus den Route-Parametern geladen werden sollte, bereits vorhanden ist. Ist dies nicht der Fall, wird die Funktion über setTimeout erneut aufgerufen. Hier muss man sich auch eine Referenz auf die aktuelle Komponente zwischenspeichern, da in dem Callback nicht this als Referenz benutzt werden kann. Diese Lösung funktioniert zwar, einen "Schönheitspreis" würde man dafür aber nicht bekommen 🫣

Lösung 2: Verwendung von combineLatest in RxJS

Eine elegantere Lösung bietet RxJS mit combineLatest. Diese Methode kombiniert die neuesten Werte mehrerer Observables.

import { combineLatest } from 'rxjs';
import { ActivatedRoute } from '@angular/router';

combineLatest([
    this.activatedRoute.params, 
    this.activatedRoute.queryParams
]).subscribe(values => {
    const [params, query] = values;
    const { id } = params;
    const { dingDong } = query;
    this.foobar = this.loadFoobar(id);
    this.foobar.setDingDong(dingDong);
});

Mit combineLatest erhält man die zuletzt emittierten Werte der Observables, in diesem Fall params und queryParams der ActivatedRoute. Dadurch kann man sicherstellen, dass beide Parameter gleichzeitig verfügbar sind und entsprechend verarbeitet werden können.