store icon indicating copy to clipboard operation
store copied to clipboard

🐞[BUG]: Dispatch action handler returns after changing the state

Open omirobarcelo opened this issue 5 years ago • 4 comments

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

omirobarcelo avatar Feb 21 '20 08:02 omirobarcelo

Thank you for reporting this. Out of interest, what is your use case where the order of these matters?

markwhitfeld avatar Feb 21 '20 10:02 markwhitfeld

@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.

omirobarcelo avatar Feb 21 '20 11:02 omirobarcelo

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.

ahnpnl avatar Feb 23 '20 08:02 ahnpnl

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

Krismix1 avatar Nov 10 '21 13:11 Krismix1

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.

arturovt avatar Dec 24 '22 19:12 arturovt