为什么我的 RxJS 倒计时时钟没有显示?

mul*_*123 0 rxjs angular

我正在开发一个 Angular 9 测验应用程序,我正在使用 RxJS 作为倒数计时器(在容器\记分板\时间\time.component.ts 中),但计时器似乎没有显示。stopTimer() 函数应该在计时器停止的秒数上停止计时器。选择正确答案后计时器应停止,并且计时器应在问题之间重置。每个问题经过的时间应保存到 elapsedTimes 数组中。请在 Stackblitz 上的 TimeComponent 中查看我的计时器代码:https ://stackblitz.com/edit/angular-9-quiz-app 。谢谢你。

下面的代码是我第一次用 RxJS 构建倒计时时钟。我最近的代码在 Stackblitz 上。

  countdownClock() {
    this.timer = interval(1000)
      .pipe(
        takeUntil(this.isPause),
        takeUntil(this.isStop)
      );
    this.timerObserver = {
      next: (_: number) => {
        this.timePerQuestion -= 1;
          ...
        }
    };

    this.timer.subscribe(this.timerObserver);
  }

  goOn() {
    this.timer.subscribe(this.timerObserver);
  }

  pauseTimer() {
    this.isPause.next();
    // setTimeout(() => this.goOn(), 1000)
  }

  stopTimer() {
    this.timePerQuestion = 0;
    this.isStop.next();
  }
Run Code Online (Sandbox Code Playgroud)

我在 TimerService 中使用了 stopTimer() 和 pauseTimer(),因此我可以从不同的组件调用它们。

And*_*tej 5

与每个复杂的问题一样,您必须将其分解为更小的、可消化的问题。

因此,我创建了一个重新创建基本功能的StackBlitz演示:

  • 启动计时器
  • 暂时停止(标记时间戳);for example when all answers would be selected
  • 从上一个时间戳继续
  • 重置计时器
  • 停止计时器

这是代码:

const $ = document.querySelector.bind(document);

const start$ = fromEvent($('#start'), 'click').pipe(shareReplay(1));
const reset$ = fromEvent($('#reset'), 'click');
const stop$ = fromEvent($('#stop'), 'click');
const markTimestamp$ = fromEvent($('#mark'), 'click');
const continueFromLastTimestamp$ = fromEvent($('#continue'), 'click');

const src$ = concat(
  start$.pipe(first()),
  reset$
).pipe(
  switchMapTo(
    timer(0, 1000)
      .pipe(
        takeUntil(markTimestamp$),
        repeatWhen(
          completeSbj => completeSbj.pipe(switchMapTo(
            continueFromLastTimestamp$.pipe(first())
          ))
        ),
        scan((acc, crt) => acc + 1000, 0)
      )
  ),
  takeUntil(stop$),
  repeatWhen(completeSbj => completeSbj.pipe(switchMapTo(start$.pipe(skip(1), first()))))
).subscribe(console.log)
Run Code Online (Sandbox Code Playgroud)

让我们来看看每个相关部分。


concat(
  start$.pipe(first()),
  reset$
).pipe(switchMapTo(timer(...)))
Run Code Online (Sandbox Code Playgroud)

timer会从0开始计数,只有当它尚未启动之前(start$.pipe(first())),或者用户想要重置的一切(reset$)。

concat(a$, b$)确保b$除非a$完成,否则不能发出。


timer(0, 1000)
  .pipe(
    takeUntil(markTimestamp$),
    repeatWhen(
      completeSbj => completeSbj.pipe(switchMapTo(
        continueFromLastTimestamp$.pipe(first())
      ))
    ),
    scan((acc, crt) => acc + 1000, 0)
  )
Run Code Online (Sandbox Code Playgroud)

我们希望计时器一直处于活动状态,直到markTimestamp$发出。当这种情况发生时,source( timer(0, 1000)) 将被取消订阅。随着repeatWhen我们可以决定什么时候应该timer进行重新订阅。也就是说,当continueFromLastTimestamp$.pipe(first())发射时。我们使用 很重要first(),否则源可能会被多次重新订阅。

配售scan((acc, crt) => acc + 1000, 0)后,repeatWhen确保了最后的时间戳不会丢失。例如,计时器可能从 开始X,在X+5用户触发时markTimestamp$,然后在X + 100用户触发时continueFromLastTimestamp$。发生这种情况时,计时器将发出X+6.


takeUntil(stop$),
repeatWhen(completeSbj => completeSbj.pipe(switchMapTo(start$.pipe(skip(1), first()))))
Run Code Online (Sandbox Code Playgroud)

计时器应该处于活动状态,直到stop$发出。然后,只有用户start$再次触发它才能重新启动。skip(1)用来因为我们不被缓存值ReplaySubject使用start$shareReplayfirst()被使用,因为重新订阅 一次