store
store copied to clipboard
🐞[BUG]: Dispatch action handler returns after changing the state
Affected Package
The issue is caused by package @ngxs/store, the Actions class.
Description
According to the docs (https://www.ngxs.io/advanced/action-handlers):
The action handler is an Observable that receives all the actions dispatched before the state takes any action on it.
But the actions with status DISPATCHED
are emitted after the state has already changed.
🔬 Minimal Reproduction
https://stackblitz.com/edit/ngxs-simple-demo-fm31wo
I imagined that the state in state on dispatched action
would show the state before the action is applied.
Environment
Libs:
- @angular/core version: 8.2.14
- @ngxs/store version: 3.6.2
Browser:
- [X] Chrome (desktop) version 80.0.3987.106
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
For Tooling issues:
- Node version: 12.13.0
- Platform: Windows 10
Thank you for reporting this. Out of interest, what is your use case where the order of these matters?
@markwhitfeld I'm trying to implement an undo function. I wanted to save the state before applying the action (since I haven't received an answer on saving just the changes https://github.com/ngxs-labs/immer-adapter/issues/373).
Right now I opted to use a plugin, but using the action handler would be much better.
I also observed similar thing when using ofActionDispatched operator. I was wondering if that is design intended but imo, when action is dispatched, the state shouldn’t change til action is successful.
I have found myself also needing order-dependant logic. My use case goes as follows:
I need to observe the changes of state until a certain action is triggered. The problem is that even with ofActionDispatched
, the action is only caught after the state was modified. Thus, the final observable emits more values than desired.
Simplified version of my logic:
function build() {
return combineLatest([this.subject$, this.stateObs$]).pipe(
takeUntil(this.actionStream$.pipe(ofActionDispatched(MyAction))),
map(
([val1, val2]) => val1 && val2 ),
);
}
In my case, adding a delay and moving operators around like pipe(map(...), delay(100), takeUntil(...))
fixes the issue, but this is just a workaround.
The actual behaviour contradicts with the order of events described in the Action Life-cycle docs https://www.ngxs.io/advanced/actions-life-cycle
This is by design. There's no way to make the Actions
stream emit the DISPATCHED
value before the state is updated. NGXS internally subscribes to the Actions
stream too, and filters DISPATCHED
actions to invoke their handlers and update the state. When you subscribe to the Actions
stream, the second subscriber is called synchronously after the state has been updated since it's been updated by the first subscriber, which is NGXS.
We would need to re-order observers in the matter they're subscribing to the Actions
stream, which doesn't overcome the ROI of this software decision.