Home > Mobile >  Angular NgRx component store. switchMap emits the old value first
Angular NgRx component store. switchMap emits the old value first

Time:12-10

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.

See the working example

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:

  1. The first subscription that occur is in ngOnInit where you subscribe on isSuccess$
  2. Then second subscription happens on the count$ when the template is compiled (in the html template)
  3. It executes pipe of your count$ select and eventually shareReplay remembers the value
  4. Then setState is executed and sets isSuccess = true and count = 1
  5. Here the most important part starts: isSuccess is pushed and you get notified in your filter and this happens synchronously
  6. Then you, again, synchronously get into your switchMap and this operator will synchronously subscribe on the count$
  7. Since count$ is already cached via shareReplay 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

  • Related