I have a flag isSuccess
in the state to indicate that the entity is successfully set. I want to read the entity's actual value only when isSuccess
true.
To do this I made such expression:
this.store.success$
.pipe(
filter((isSuccess) => isSuccess),
switchMap(() => this.store.count$)
)
.subscribe((count) => console.log(count));
the patch action changes isSuccess
and count
simultaneously
change() {
this.setState((state) => ({ count: state.count 1, isSuccess: true }));
}
But for some reason the old value is emitted first and then the new one.
It is looking very weird to me, because such behavior is not an issue for the global store.
I checked updated
function source code and see that a ReplaySubject used for the state storage. There is only one .next
in the func with the next code: tap((newState) => this.stateSubject$.next(newState)),
. I tried to do the same but with my own ReplaySubject
and it works as expected. Here is an example..
What makes the old value to emit and how can I only have my actual value be emitted?
CodePudding user response:
Because you are storing the values in the count state, you can use map or startWith but personally I think startWith pipe will do the job - will always emit the first value:
switchMap(() => this.store.count$.pipe(startWith(0))) // this will always give you first value.
More information: https://rxjs.dev/api/operators/startWith
CodePudding user response:
The root cause is not in the updater
function. It is in how the ComponentStore.select
function build the observable. Internally it uses shareReplay
operator (source code). And if you add this operator to your second example you will get the same result.
So what exactly happens:
- The first subscription that occur is in
ngOnInit
where you subscribe onisSuccess$
- Then second subscription happens on the
count$
when the template is compiled (in the html template) - It executes pipe of your
count$
select and eventuallyshareReplay
remembers the value - Then
setState
is executed and setsisSuccess = true
andcount = 1
- Here the most important part starts: isSuccess is pushed and you get notified in your filter and this happens synchronously
- Then you, again, synchronously get into your
switchMap
and this operator will synchronously subscribe on thecount$
- Since
count$
is already cached viashareReplay
just when you subscribe on it will give you the old result because RxJs hasn't started recalculating it for the existing subscription
If you remove that subscription from html template you will get it work properly. But it not a solution. What you need to do is to make this change with 'isSuccess' to be async
which means the actual change will be delivered after all synchronous code complete (where a recalculating of your count$
is a part of it). You can achieve this by placing .pipe(observeOn(asapScheduler))
It will move the emission of the value to the async queue of the event loop
and the result will be correct
Here is the corrected example