【问题标题】:Shared countdown timer observable可观察到的共享倒计时计时器
【发布时间】:2020-11-24 10:09:37
【问题描述】:

我有一个基本的可观察计时器,我想在多个订阅者之间共享。

time = 30;
timer$ = timer(0,1000).pipe(
    map(i => this.time - i),
    take(this.time + 1),
    finalize(() => console.log('DONE')),
    share()
);

我在模板和组件中都订阅了 observable,在组件中启动计时器,在模板中显示剩余时间。

ngOnInit() {
    this.runTimer();
}

runTimer() {
    this.timer$.subscribe();
}

{{ timer$ | async }}

这有效,但只是第一次。如果我再次调用 runTimer() 函数(例如触发该调用的按钮),计时器会再次启动,但更改不会反映在我看到 0 的模板中(可能是因为前一个计时器已完成)。

我做错了什么?

我创建了 stackblitz:https://stackblitz.com/edit/angular-ivy-qc759v?embed=1&file=src/app/app.component.ts

【问题讨论】:

  • 这能回答你的问题吗? Angular 2 Reset Observable Timer
  • 我不明白。 timer$ 是否会因为我有 take(N) 而自动取消订阅?我从接受的答案中尝试了解决方案,但它对我不起作用(参见 stackblitz)。我真的需要一个额外的 observable(Subject) 来让它工作吗?
  • 它破坏了我的浏览器,进入无限循环
  • 你不想要计时器,你想要倒计时,不是吗?
  • 这个答案有一个秒表,您可以开始、停止和重置。如果您使用它而不是timer,那么您就有了一种强大的方法来重置倒计时。 stackoverflow.com/questions/64676617/pause-an-interval-rxjs/…

标签: angular rxjs rxjs-observables


【解决方案1】:

经过一段时间的努力,我找到了解决方案。

这可能不是最好的解决方案,但它确实有效。

time = 5;

  timer$: Observable<number>;
  timerSub: Subscription;

  ngOnInit(): void {
    this.startTimer();
  }

  startTimer() {
    this.timerSub && this.timerSub.unsubscribe();
    this.timer$ = timer(0, 1000).pipe(
      map(i => {
        console.log(this.time - i);
        return this.time - i;
      }),
      take(this.time + 1),
      finalize(() => console.log("DONE")),
      share()
    );
    this.timerSub = this.timer$.subscribe();
  }

Stackblitz:https://stackblitz.com/edit/angular-ivy-ibkbcu?file=src%2Fapp%2Fapp.component.ts

【讨论】:

    【解决方案2】:

    我认为问题在于异步,您可以创建一个返回计时器的函数

      timer$(){
        return timer(0, 1000).pipe(
        map(i => {
          console.log(this.time - i);
          return this.time - i;
        }),
        take(this.time + 1),
        finalize(() => console.log("DONE")),
        share()
      );
      }
      //define a new observable
      timerApp$
    
      ngOnInit(): void {
        this.startTimer();
      }
    
      startTimer() {
        this.timerApp$=this.timer$();
      }
    }
    

    并使用

    {{timerApp$ |async}}
    

    ::glups:: 答案和你找到的一样

    【讨论】:

      【解决方案3】:

      根据您的代码map(i =&gt; this.time - i),我相信您想要的不是计时器,而是倒计时。所以我会去做一个带有倒计时的演示,但如果你真的想要一个计时器,你应该能够使用非常相似的倒计时代码来解决它。

      使用 observable 时,尽量不要:

      • 重新分配可观察值
      • 使用subscribe

      这是我的倒计时建议:

      TS端:

      @Component({
        selector: "my-app",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.css"]
      })
      export class AppComponent {
        public countdownControl: FormControl = new FormControl(5);
      
        public startCountdown$$: Subject<void> = new Subject();
      
        public countdown$: Observable<number> = this.startCountdown$$.pipe(
          withLatestFrom(this.countdownControl.valueChanges.pipe(startWith(this.countdownControl.value))),
          switchMap(([_, countdownStartValueSeconds]) =>
            timer(0, 1000).pipe(
              map((_, index) => countdownStartValueSeconds - index),
              takeWhile(v => v >= 0)
            )
          )
        );
      }
      

      HTML 方面:

      <h1>{{ countdown$ | async }}</h1>
      
      Number of seconds for the countdown:
      <input type="number" step="1" [formControl]="countdownControl">
      
      <button (click)="startCountdown$$.next()">Start again</button>
      

      请注意,我只分配了一次可观察值,之后再也不分配,而且根本没有订阅。

      如果开始时间是静态的,您可以通过删除 withLatestFrom 来稍微简化此代码,但我认为这是一个很好的示例,可以展示如何为开始时间更新变量。

      注意:当然(感谢这里的switchMap),如果计时器正在运行并且您想重新启动它,只需再次按下开始按钮即可。

      这是一个工作演示:https://stackblitz.com/edit/angular-ivy-gvsn66?embed=1&file=src/app/app.component.ts

      【讨论】:

        猜你喜欢
        • 2018-07-20
        • 1970-01-01
        • 2018-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多