Maintain Self-resetting State in Your Observable Streams using the RxJS scan Operator

InstructorRares Matei

Share this video with your friends

Send Tweet

Our manager, unaware of how much our functional reactive approach has kept us delivering these features on time and bug-free, decides they want a small new feature added to the spinner before the next release: a percentage indicator, showing how many tasks have finished out of the total started since the spinner was last shown. So in this lesson we will be looking at combining the existing observables that gives the up to date count of tasks, together with a more advanced use of the scan operator, to create a stream that tells us the total count of tasks that have been launched since the spinner was last shown, and how many of them have completed. We’ll also take advantage of scan’s disposal behavior to reset its state when the spinner hides.

~ 3 years ago

Hi,

I have started the task on my own, just for a study, but I encountered an issue, would you be able to explain it?

This is my solution:

const spinnerTotal = shouldShowSpinner.pipe(
  mergeMapTo(currentLoadCount),
  tap((...args) => console.log('currentLoadCount', args)),
  scan(
    (acc, currentLoadCountInScan) => {

      if (acc.isFirst) {
        acc.isFirst = false;
        acc.total = currentLoadCountInScan;
      } else {
        switch (true) {
          case (acc.prev < currentLoadCountInScan):
            acc.total += 1;
            break;
          case (acc.prev > currentLoadCountInScan):
            acc.completed += 1;
            break;
        }
      }

      console.log('acc.total', acc.total);
      console.log('acc.completed', acc.completed);

      acc.prev = currentLoadCountInScan;

      return acc;

      // acc.total = currentLoadCountInScan > acc.total ? currentLoadCountInScan : acc.total;
      // acc.completed = currentLoadCountInScan > acc.total ? currentLoadCountInScan : acc.total;
    },
    {
      total: 0,
      completed: 0,
      prev: 0,
      isFirst: true,
    }
  ),
  // TODO Via react component
  tap(({ total }) => {
    let el = document.createElement('h1');
    el.id = '123';
    el.style.position = 'absolute';
    el.style.right = 0;
    el.style.bottom = 0;

    if (document.getElementById('123')) {
      el.remove();
      el = document.getElementById('123');
    } else {
      document.body.appendChild(el);
    }

    el.innerText = total;
  })
);

let s = spinnerTotal
  .pipe(
    switchMap(({ total, completed }) => {
      return showSpinner(total, completed)
        .pipe(
          takeUntil(shouldHideSpinner)
        );
    }),
  )
  .subscribe({
    next: () => {
      console.log('next', s);
    },
    complete () {
      console.log('complete');
    }
  });

The problem is that scan state is not refreshed and I cannot understand why is that? What am I doing wrong?

To compare with your solution my just starts to observe via shouldShowSpinner then I do mergeMapTo(currentLoadCount) to get the numbers, then in the end I switch map it to showSpinner.

I created a pull request in your repo https://github.com/rarmatei/egghead-thinking-reactively/pull/19

Thanks!