KamLar
KamLar

Reputation: 428

Observer - get the most last value

I need to send a request with a large amount of data. Those data come from 6 different components - so basically it is very hard to do it without a reactive approach. I decided to do something like ngrx component store implementation (because of some reasons I've decided to implement it on my own).

I've created a store implementation that is being provided as a component provider, created a state which is being held in BehaviorSubject, created a simple select function which maps the state to needed data. In ngOnInit Parent Component I made a request to the backend server to fetch foos, setting a foo.isLoading flag. Then I am subscribing and listened to foo.data changes. If it does - I am calling backend server - I am setting boo.isLoading and I am fetching boos.

Foos and Boos are fetched correctly but there is an issue with boo.isLoading flag - and I believe it comes from the order of calling state observers. Steps:

  1. foo.isLoading is set to true

  2. foo.data is set to backend response, foo.isLoading is set to false

  3. then foo observer receives value and makes an HTTP request to get boos.

  4. boo.isLoading is set to true and sends an HTTP request

  5. boo.isLoading observers receives true

  6. but since the state was changed twice - boo.isLoading observers haven't received the first value which now they do, so the most last received value is false

Any ideas on how to solve it? I've tried with switchMap state to of(state) so the previous subscription should be canceled when the new value "arrives" but it doesn't work.

Source code is here: https://stackblitz.com/edit/angular-ivy-qgbdxx

Upvotes: 0

Views: 315

Answers (1)

akotech
akotech

Reputation: 2274

The problem is that you are using a read to trigger a write in this selector.

this.parentStore.select(state => state.foo.data).pipe(
   filter(data => !!data),
   tap(_ => this.parentStore.loadBooData())
).subscribe();

And since FooLoadCompletion and BooLoadStart processes are synchronous, the order of execution is messing with your flow.

I'll try to explain it.

The FooLoadComplete triggers the execution of the following selectors in this order, due to the moment they subscribe.

onFooLoadComplete
   selector(foo.data).next(['a','b','c','d'])
   selector(foo.isLoading).next(false)
   selector(boo.isLoading).next(false)

But since your triggering the BooLoadStart side effect in the foo.data selector the execution order ends up being like this.

onFooLoadComplete
  selector(foo.data).next(yourData)
    BooLoadStart
      selector(foo.isLoading).next(false)
      selector(boo.isLoading).next(true)
  selector(foo.isLoading).next(false)
  selector(boo.isLoading).next(false)

So the boo.isLoading values get processed in the selector in the inverse order that you expect. And that is why the selector is printing false.

To avoid this kind of problems you should keep your READ and WRITE pipelines independent.

In the code your supplying you could solve it removing the foo.data selector side effect and calling to loadBooData in the setTimeout of loadFooData

loadFooData() {
  this.state.next({
    ...this.state.value,
    foo: {
      ...this.state.value.foo,
      isLoading: true
    }
  });

  setTimeout(() => {
    this.state.next({
      ...this.state.value,
      foo: {
        ...this.state.value.foo,
        isLoading: false,
        data: ['a', 'b', 'c', 'd']
      }
    })

    this.loadBooData();

  }, 2000)
}

Hope to have helped

cheers

Upvotes: 1

Related Questions