Reputation: 8784
I have two actions: validate and update. Both can be dispatched independently, but I would also like that if the update action is dispatched, it first dispatches the validate action and only continues if there were no validations.
If this were synchronous code it would be something like:
if (this.backendService.isValid(dataset)) {
this.backEndService.update(dataset)
}
The definition of the two actions (and their corresponding success and error actions):
export const validateDataset = createAction('[Datasets API] Validate Dataset', props<{ dataset: DataSetDto }>());
export const validateDatasetSuccess = createAction('[Datasets API] Validate Dataset Success', props<{ validationErrors: SgErrorMessage[]}>());
export const validateDatasetFail = createAction('[Datasets API] Validate Dataset Fail', props<{ error: string }>());
export const updateDataset = createAction('[Datasets API] Update Dataset', props<{ dataset: DataSetDto }>());
export const updateDatasetSuccess = createAction('[Datasets API] Update Dataset Success');
export const updateDatasetFail = createAction('[Datasets API] Update Dataset Fail', props<{ errorEntity: SgErrorEntity }>());
Attempt 1
If I have an effect listening for updateDataset
and attempts to dispatch first validateDataset
it never seems to get used when .pipe()
is applied (and probably here I should use a selector like hasValidationErrors
returning bool instead of also using filter
operator):
updateDataset$ = createEffect(() => {
return this.actions$.pipe(
ofType(DatasetApiActions.updateDataset),
mergeMap(action => of(DatasetPageActions.validateDataset({dataset: action.dataset})).pipe(
withLatestFrom(this.store.pipe(select(getValidationErrors))),
filter(([action, validationErrors]) => validationErrors.length > 0),
mergeMap(([action, validationErrors]) => this.datasetService.editDataSet(action.dataset).pipe(
map(() => DatasetApiActions.updateDatasetSuccess()),
catchError(error => of(DatasetApiActions.updateDatasetFail({error})))
))
))
);
});
Attempt 2
I tried to instead I split it up into a saveDataset
action that should call both validateDataset
and updateDataset
actions sequentially, and also filter the updateDataset
to only run if the validationErrors
state is empty. I used concatMap
so the actions could trigger sequentially and they probably are but the success/fail responses come in later and so the validation data can't be used:
saveDataset$ = createEffect(() => {
return this.actions$.pipe(
ofType(DatasetPageActions.saveDataset),
concatMap(action => concat(
of(DatasetPageActions.validateDataset({dataset: action.dataset})),
of(DatasetApiActions.updateDataset({dataset: action.dataset}))
))
);
});
updateDataset$ = createEffect(() => {
return this.actions$.pipe(
ofType(DatasetApiActions.updateDataset),
withLatestFrom(this.store.pipe(select(getValidationErrors))),
filter(([action, validationErrors]) => validationErrors.length == 0),
tap(([action, validationErrors]) => console.log("validationErrors: " + validationErrors))
);
}, {dispatch: false});
Maybe I'm thinking about it the wrong way and the validation should be built as an async forms validation instead, but I still think the question is valid.
What I'm struggling with is that I can't add an effect to the validateDatasetSuccess
action to trigger the updateDataset
action because that may be dispatched by the user separately (and not only by saving). Is there any way to achieve this behaviour?
Upvotes: 2
Views: 3100
Reputation: 11934
You could try this:
updateDataset$ = createEffect(() => {
return this.actions$.pipe(
ofType(DatasetApiActions.updateDataset),
// dispatch the `validate` action
tap(action => this.store.dispatch(DatasetPageActions.validateDataset({dataset: action.dataset}))),
switchMap(
action => this.store.pipe(
select(getValidationErrors),
// we want to skip the current state
// after all, the store is just a `BehaviorSubject`,
// so subscribing to it will return the current state
skip(1),
filter(validationErrors => /* ... your condition here ... */),
mapTo(action),
),
),
mergeMap(
action => this.datasetService.editDataSet(action.dataset).pipe(
map(() => DatasetApiActions.updateDatasetSuccess()),
catchError(error => of(DatasetApiActions.updateDatasetFail({error})))
)
)
);
});
Upvotes: 2