angular - Wie man auf 2 Aktionen in @ngrx/effects wartet
Kann effect wie Promise.all auf zwei Aktionen warten? Beispiel:
@Effect()
pulic addUser() {
return this.actions$.ofType(user.ADD)
.switchMap(() => {
return this.userService.add();
})
.map(() => {
return new user.AddSuccessAction();
});
}
@Effect()
pulic addUserOptions() {
return this.actions$.ofType(userOptions.ADD)
.switchMap(() => {
return this.userOptionsService.add();
})
.map(() => {
return new userOptions.AddSuccessAction();
});
}
@Effect()
public complete() {
return this.actions$.ofType(user.ADD_SUCCESS, userOptions.ADD_SUCCESS)
// how to make it works like Promise.all ?
.switchMap(() => {
return this.statisticService.add();
})
.map(() => {
return new account.CompleteAction();
});
}
AKTUALISIERT Was ich erreichen möchte, ist ein ähnliches Verhalten wie bei Promise.all. Wie kann man zwei Effekte parallel ausführen, warten, bis alle Effekte aufgelöst sind, und dann eine dritte Aktion ausführen. Etwas wie https://redux-saga.js.org/docs/advanced/RunningTasksInParallel.html Mit Promises war es ziemlich offensichtlich:
Promise.all([fetch1, fetch2]).then(fetch3);
Ist es in ngrx/effects möglich? Oder ist es ein falscher Weg in ngrx/effects?
ANTWORT
Es gibt wenige Optionen, die Sie verwenden können:
1) Verwenden Sie keine allgemeinen Aktionen.
Befolge diese Regeln aus Myke Ryans Präsentation: https://youtu.be/JmnsEvoy-gY
Vorteile: einfacher zu debuggen
Nachteile: jede Menge Boilerplate und Aktionen
2) Verwenden Sie einen komplexen Stream mit verschachtelten Aktionen.
Sieh dir diesen Artikel an: https://bertrandg.github.io/ngrx-effects-complex-stream-with-nested-actions/
Hier ist ein einfaches Beispiel für zwei Aktionen:
@Effect()
public someAction(): Observable<Action> {
return this.actions$.pipe(
ofType(actions.SOME_ACTION),
map((action: actions.SomeAction) => action.payload),
mergeMap((payload) => {
const firstActionSuccess$ = this.actions$.pipe(
ofType(actions.FIRST_ACTION_SUCCESS),
takeUntil(this.actions$.pipe(ofType(actions.FIRST_ACTION_FAIL))),
first(),
);
const secondActionsSuccess$ = this.actions$.pipe(
ofType(actions.SECOND_ACTION_SUCCESS),
takeUntil(this.actions$.pipe(ofType(actions.SECOND_ACTION_FAIL))),
first(),
);
const result$ = forkJoin(firstActionSuccess$, secondActionsSuccess$).pipe(
first(),
)
.subscribe(() => {
// do something
});
return [
new actions.FirstAction(),
new actions.SecondAction(),
];
}),
);
}
Vorteile: Sie können erreichen, was Sie wollen
Nachteile: komplexer Stream ist zu komplex, um unterstützt zu werden :) sieht hässlich aus und kann schnell ins Chaos geraten, Observables werden sich nicht abmelden, bis Erfolgs- oder Fehleraktionen eintreten, das bedeutet, dass theoretisch beliebige Drittanbieteraktionen Signale an diese Observables senden können.
3) Verwenden Sie das Aggregatormuster.
Sehen Sie sich Victor Savkins Präsentation über Zustandsverwaltungs-Muster und Best Practices mit NgRx an: https://www.youtube.com/watch?v=vX2vG0o-rpM
Hier ist ein einfaches Beispiel:
Zuerst müssen Sie Aktionen mit dem Parameter correlationId erstellen. Die CorrelationId sollte einzigartig sein, sie kann zum Beispiel eine GUID sein. Diese ID werden Sie in Ihrer Aktionskette verwenden, um Ihre Aktionen zu identifizieren.
export class SomeAction implements Action {
public readonly type = SOME_ACTION;
constructor(public readonly correlationId?: string | number) { }
// if you need payload, then make correlationId as a second argument
// constructor(public readonly payload: any, public readonly correlationId?: string | number) { }
}
export class SomeActionSuccess implements Action {
public readonly type = SOME_ACTION_SUCCESS;
constructor(public readonly correlationId?: string | number) { }
}
export class FirstAction implements Action {
public readonly type = FIRST_ACTION;
constructor(public readonly correlationId?: string | number) { }
}
export class FirstActionSuccess implements Action {
public readonly type = FIRST_ACTION_SUCCESS;
constructor(public readonly correlationId?: string | number) { }
}
// the same actions for SecondAction and ResultAction
Dann unsere Effekte:
@Effect()
public someAction(): Observable<Action> {
return this.actions$.pipe(
ofType(actions.SOME_ACTION),
mergeMap((action: actions.SomeAction) => {
return [
new actions.FirstAction(action.corelationId),
new actions.SecondAction(action.corelationId),
];
}),
);
}
@Effect()
public firstAction(): Observable<Action> {
return this.actions$.pipe(
ofType(actions.FIRST_ACTION),
switchMap((action: actions.FirstAction) => {
// something
...map(() => new actions.FirstActionSuccess(action.correlationId));
}),
);
}
// the same for secondAction
@Effect()
public resultAction(): Observable<Action> {
return this.actions$.pipe(
ofType(actions.SOME_ACTION),
switchMap((action: actions.SomeAction) => {
const firstActionSuccess$ = this.actions$.pipe(
ofType(actions.FIRST_ACTION_SUCCESS),
filter((t: actions.FirstActionSuccess) => t.correlationId === action.correlationId),
first(),
);
const secondActionsSuccess$ = this.actions$.pipe(
ofType(actions.SECOND_ACTION_SUCCESS),
filter((t: actions.SecondActionSuccess) => t.correlationId === action.correlationId),
first(),
);
return zip(firstActionSuccess$, secondActionsSuccess$).pipe(
map(() => new actions.resultSuccessAction()),
)
}),
);
}
Vorteile: dasselbe wie Punkt 2, aber keine Maßnahmen Dritter.
Contra: dasselbe wie Punkt 1 und 2
4) Verwenden Sie keine Effekte für die API. Verwenden Sie die altbewährten Dienste, die Effekte emulieren, aber Observable zurückgeben.
Zu Ihren Diensten:
public dispatchFirstAction(): Observable<void> {
this.store.dispatch(new actions.FirstAction(filter));
return this.service.someCoolMethod().pipe(
map((data) => this.store.dispatch(new actions.FirstActionSuccess(data))),
catchError((error) => {
this.store.dispatch(new actions.FirstActionFail());
return Observable.throw(error);
}),
);
}
So kannst du es später überall kombinieren, zum Beispiel:
const result1$ = this.service.dispatchFirstAction();
const result2$ = this.service.dispatchSecondAction();
forkJoin(result1$, result2$).subscribe();
5) Verwenden Sie ngxs: https://github.com/ngxs/store
Vorteile: weniger Boilerplate, das fühlt sich wie Angular-Sachen an, es wächst schnell
Nachteile: hat weniger Funktionen als ngrx
Antwort #1
Ich bin neu bei RXJS, aber was ist mit diesem?
Sie können {dispatch: false} entfernen, wenn Sie das tap in ein switchMap ändern.
@Effect({dispatch: false})
public waitForActions(): Observable<any> {
const waitFor: string[] = [
SomeAction.EVENT_1,
SomeAction.EVENT_2,
SomeAction.EVENT_3,
];
return this._actions$
.pipe(
ofType(...waitFor),
distinct((action: IAction<any>) => action.type),
bufferCount(waitFor.length),
tap(console.log),
);
}
Antwort #2
Das hat bei mir in ngrx 8 funktioniert
waitFor2Actions$ = createEffect(() =>
combineLatest([
this.actions$.pipe(ofType(actions.action1)),
this.actions$.pipe(ofType(actions.action2)),
]).pipe(
switchMap(() => ...),
)
);
