Redux-Observable: Epics

Ein paar Dinge, die wir über das Arbeiten mit Epics gelernt haben

Debuggen

Vorwort: Da Epics in Redux-Observable nur einmal zur initialisierung aufgerufen werden, ist es nicht einfach wie gewohnt möglich diese zur Laufzeit mit sinnvollen Daten zu debuggen.

Die Troubleshooting Dokumentation zeigt bereits einige Möglichkeiten an die Daten zu kommen, wenn diese durch die Epics geleitet werden.

Die folgende fehlerhafte! und naive Funktion

 

export function requestItemsOnSecondaryFilter(action$, store) {
        return action$.pipe(
            ofType(actions.SET_SECOND_CATEGORY),
            tap(item => console.log(item)),
            mapTo(actions.requestItems(store.value.categories.selectedSecondCategoryId, 1)),
            tap(item => console.log(item)),
    )
}

 

zeigt was passiert, wenn man die tap Funktion von RxJS benutzt. Die Erste Action ist noch die SET_SECOND_CATEGORY action.

Diese Action wird dann nach dem Epic Prinzip "Action in, Action out" per Actioncreator zu einer request Item Action umgebaut

Wie kommt man an die Daten?

Die nächste wichtige Erkentnis ist an die Payload, also die Daten der Action zu kommen.

Im Beispiel von Oben wird auf den Wert über den Store mitgegeben, aber das ist ehr unschön und man muss eben diesen Wert im State immer mitpflegen

 

Da die Beispiele von redux-observable immer mit der arrow function sind, sollte man diese auch für die eigene Entwicklung verweden. Was einem jedoch nicht gesagt wird ist die Folgende Erkentniss:

 

const pingEpic = action$ => action$.pipe(
  filter(action => action.type === 'PING'),
  mapTo({ type: 'PONG' })
);

 

Hier haben wir 2 Variablen zur verfügung

action$ oder besser gesagt actionstream (per Konvention)

action welche per arrow function eigentlich erst erzeugt wird (?)

Diese action variable beinhaltet den action payload und weitere Daten, die im Actioncreator definiert wurden.

 

Hier nochmal die unsere Funktion (diesmal als const)

 

export const requestItemsOnSecondaryFilter = (action$, store) => (
    action$.pipe(
        ofType(actions.SET_SECOND_CATEGORY),
        tap(item => console.log(item)),
        map(action => actions.requestItems(action.payload)),
        tap(item => console.log(item)),
    )
);

 

Die Ausgabe in der Console ist die gleiche wie Oben, aber diesmal wird der Wert aus der SET_SECOND_CATEGORY action.payload ausgelesen, anstatt direkt aus dem Store

 

Per Epic mehrere Actions dispatchen

Im normalen JS Code kann man einfach Actions hintereinander und ganz normal per dispatcher auslösen.

 

Wenn man das ganze in einem Epic machen will, hat man das Problem wieder "Action in, Action out". Man darf also per Design nur eine Action dispatchen.

Die Lösung dafür ist unter anderem flatMap und concat

 

import {flatMap} from 'rxjs/operators';
import {of, concat} from 'rxjs';

flatMap(action =>
    //create an observable of the two action calls
    /*observable*/concat(
        /*observable*/of(actions.setFirstCategory(.....),
        /*observable*/of(actions.setSecondCategory(.....)
    )   
)

 

RxJS 6 hat diese gänderten Importe. Die meisten Codebeispiele online haben noch Observable.of(....), daher die Kommentare im Code

Die Kombination aus flatMap und concat macht aus 2 Actions eine (Magic!)

das of wandelt die Action in ein Observable um, damit es überhaupt vom Epic benutzbar ist und keine Fehlermeldung gibt

 

Laut diesem Beitrag sollte die of Funktion auch in der Lage sein mehrere Actions nacheinander zu dispatchen.

arrow function Magic

export function doMagicThings(action$, store) {
    console.log(store.value.categories.selectedSecondCategoryId);
    return action$.pipe(
        ofType(actions.SET_FIRST_CATEGORY),
        map(action => actions.requestItems(store.value.categories.selectedSecondCategoryId)),
    )
}

 

In diesem Epic passieren unverständliche Dinge

Der erste Wert des Stores ist aus mir unbekannten Gründen der default Wert, den ich beim erstellen des States eingebe.

Der zweite Wert, der in der requestItems Actions übergeben wird, ist der tatsächliche aktuelle Wert des Stores. Wenn man sich nochmal den Wert des Stores nach dem map aufruf ausgeben lässt, erhält man wieder den ersten, falschen Wert