2017-12-16 5 views
1

Mein Szenario ist eine klassische Webseite mit einem Suchformular und einer Ergebnisliste. Ich möchte das Verhalten des Ladens der Ergebnisse in ein Observable einkapseln.RxJs: Muster für beobachtbare Suchergebnisse

Hier ist, was ich zur Zeit in Typoskript tun:

function loadResults(query): Observable<T[]> {} 

const results = new Subject<ResultEvent<T[]>>(); 

const results: Observable<ResultEvent<T[]>> = 
    form.valueChanges 
    .distinctUntilChanged() 
    .do(() => results.next(ResultEvent.pending())) 
    .switchMap(query => loadResults(query)) 
    .subscribe({ 
     next: (data: T[]) => results.next(ResultEvent.present(data)), 
     error: err => results.next(ResultEvent.failed(err)), 
    }); 

Die Idee ist, dass results immer den aktuellen Stand der Suche enthält: entweder pending, present oder failed. Wenn sich die Abfrage ändert, wird das Ergebnis auf pending festgelegt, und wenn der Dienst Daten zurückgibt, wird das Ergebnis auf present festgelegt.

Was mir an dieser Lösung nicht gefällt, ist der explizite Aufruf an subscribe(). Ich hätte lieber eine einfache Observable, die abonniert werden kann (zB in Angular mit der async Pipe), ohne eine explizite Anmeldung zu erstellen. Die Nebenwirkungen in do erscheinen auch ziemlich hacky.

const results: Obserbable<ResultEvent<T[]>> = 
    form.valueChanges.distinctUntilChanged() 
    . /* here be dragons */; 

Vielen Dank für jede Beratung und Ideen!

Antwort

2

Ich glaube, Sie etwas in dieser Richtung wollen:

const results$ = form.valueChanges 
    // This is up to you, but with user input it might make sense to 
    // give it just a little bit of time before we hit the server since 
    // most user input will be more than a single character. 
    //.debounceTime(100) 

    .distinctUntilChanged() 

    // Using switchMap guarantees that the inner observable will be 
    // cancelled if the input changed while we are still waiting for 
    // a result. Newer is always better! 
    .switchMap(query => loadResults(query) 
    // If we get data, we use it. 
    .map(results => ResultEvent.present(results)) 

    // We catch errors and turn them into a failure event. 
    .catch(err => Observable.of(ResultEvent.failed(err))) 

    // Whatever happens, first things first. 
    .startWith(ResultEvent.pending()) 
); 

ich übrigens eine debounceTime dort über das Hinzufügen, auch denken würde.

Hier ist ein Schnipsel, den Sie in https://rxviz.com kopieren können, um es in Aktion zu sehen (leider funktioniert ihre Shares Link-Funktion nicht mehr). Stellen Sie sicher, dass das Zeitfenster auf 10 Sekunden eingestellt ist.

const ResultEvent = { 
    pending:() => 'Pending', 
    failed: err => 'Error: ' + err, 
    present: data => 'Data: ' + data, 
}; 

const loadResults = query => query === 2 
    ? Rx.Observable.of(null).delay(500).switchMap(() => Rx.Observable.throw('Oops')) 
    : Rx.Observable.of(42).delay(500) 

const input$ = Rx.Observable.timer(0, 2000).take(4); 

input$.switchMap(query => loadResults(query) 
    .map(data => ResultEvent.present(data)) 
    .catch(err => Rx.Observable.of(ResultEvent.failed(err))) 
    .startWith(ResultEvent.pending()) 
) 
+0

Schöne, vielen Dank. Sieht auf diese Weise viel hübscher aus. Und ein guter Punkt über die Entprellung, ich habe es absichtlich weggelassen, um das Beispiel einfach zu halten. –

+0

@PhilippJardas Wenn es Ihre Frage beantwortet hat, betrachten Sie es bitte als akzeptiert. :-) –

+1

Sicher Sache. :-) –

Verwandte Themen