Solution 1 :

The reason it doesn’t work after clicking ‘Start’ a second time is because you are using the first() operator on your start_click observable.

This means the observable only emits on the first click, then completes.

Simply remove .pipe(rxjs.operators.first()) and your code will work each time you click the button.

However, it’s generally a good idea to avoid nested subscriptions when possible. This can help you avoid memory leaks (due not unsubscribing properly) and make the code easier to understand.

You can avoid using nested subscriptions by using one of the “Higher Order Mapping Operators” LINK. This is just a fancy way of saying: operators that map the incoming value to another observable, subscribe to it, and emit those values. They also manage these “inner subscriptions” automatically.

The switchMap operator will “switch” to a new observable whenever a new value is received. So in your case, whenever a new click is received, a new 5-second timer observable is created.

Simplified code could look something like this: Working StackBlitz

const start     = document.getElementById('start');
const countdown = document.getElementById('countdown');

const start_click = rxjs.fromEvent(start, 'click');

const time = start_click.pipe(
  tap(() => start.disabled = true),
  switchMap(() => timer(0, 1000).pipe(
    map(x => 5-x),
    take(6),
    finalize(() => start.disabled = false)
  )),
);

time.subscribe(
  x => countdown.innerText = x
);

Notice how there is only a single subscription now. We defined two different observables, start_click which is your stream of clicks and time which is your stream that emits the current value of the timer. time is defined from the start_click stream, so whenever a new click is received, under the hood a new timer gets created and emits values.

Solution 2 :

The issue is caused by rxjs.operators.first(),

try next

start_click.pipe(
    rxjs.operators.tap(() => start.disabled = true)
    , switchMap(() => rxjs.timer(0, 1000).pipe(
        rxjs.operators.skip(0) // <- do you need it?
        , rxjs.operators.take(6)
        , rxjs.operators.map(x => 5-x)
        , rxjs.operators.finalize(() => start.disabled = false)
    )),
).subscribe(x => {
    console.log('instant', x);
    let countdown = document.getElementById('countdown');
    countdown.innerText = x;
});

Problem :

I created a countdown from 5 to 0. It start when you click on the “START” button:

<html>
 <head>
  <meta charset="utf-8"/>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js">
  </script>
 </head>
 <body>
<button id="start">START</button>
COUNTDOWN:<span id="countdown"></span>
  <script>
    let start = document.getElementById('start');
    let start_click = rxjs.fromEvent(start, 'click');
    start_click.subscribe(x => console.log('click'));

start_click.pipe(rxjs.operators.first()).subscribe(
  ()=> {
    let time = rxjs.timer(0, 1000).pipe(
      rxjs.operators.skip(0)
    , rxjs.operators.take(6)
    , rxjs.operators.map(x => 5-x)
      );

    time.subscribe(x => console.log('instant', x));
    let countdown = document.getElementById('countdown');
    time.subscribe(x => countdown.innerText = x);
    start.disabled = true;

    let end = time.pipe(
    rxjs.operators.last()
    , rxjs.operators.repeatWhen(() => start_click)
    );

    end.subscribe(x=>start.disabled = false);
    start_click.subscribe(x => start.disabled = true);
    });
  </script>
 </body>
</html>

I struggle to find how to reset the countdown when the “START” button is pressed again.
I tried to add:

start_click.subscribe(x => countdown.innerText = 5);

But the value is static. Thanks.

Comments

Comment posted by tatapoil

Thank you very much! I understand!

Comment posted by tatapoil

Thanks you too, your answer helped me.

By

Leave a Reply

Your email address will not be published. Required fields are marked *