Наблюдаемый общий таймер обратного отсчета

У меня есть базовый наблюдаемый таймер, которым я хочу поделиться с несколькими подписчиками.

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

Я подписываюсь на наблюдаемое как в шаблоне, так и в компоненте, в компоненте для запуска таймера и в шаблоне, чтобы показать оставшееся время.

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


person drawingmind1    schedule 24.11.2020    source источник
comment
Отвечает ли это на ваш вопрос? Angular 2 Reset Observable Timer   -  person MoxxiManagarm    schedule 24.11.2020
comment
Я не понимаю. Таймер $ unsub автоматически, потому что у меня есть дубль (N)? Я попробовал решение из принятого ответа, но оно не работает для меня (см. Stackblitz). Мне действительно нужна дополнительная наблюдаемая (тема), чтобы она работала?   -  person drawingmind1    schedule 24.11.2020
comment
Это ломает мой браузер, заходит в бесконечный цикл   -  person drawingmind1    schedule 24.11.2020
comment
Вам не нужен таймер, вам нужен обратный отсчет, не так ли?   -  person maxime1992    schedule 24.11.2020
comment
В этом ответе есть секундомер, который вы можете ЗАПУСТИТЬ, ОСТАНОВИТЬ и СБРОСИТЬ. Если вы используете его вместо timer, у вас будет надежный способ сбросить обратный отсчет. stackoverflow.com/questions/64676617/pause-an- interval-rxjs /   -  person Mrk Sef    schedule 24.11.2020


Ответы (3)


После некоторой борьбы с этим я нашел решение.

Возможно, это не лучшее решение, но оно работает.

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

person drawingmind1    schedule 24.11.2020

Я думаю, что проблема в асинхронности, вы можете создать функцию, возвращающую таймер

  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 :: это тот же ответ, что и вы нашли

person Eliseo    schedule 24.11.2020

Основываясь на вашем коде map(i => this.time - i), я считаю, что вам нужен не таймер, а обратный отсчет. Итак, я пойду на демонстрацию с обратным отсчетом, но если вам действительно нужен таймер, вы должны иметь возможность работать с кодом обратного отсчета, как очень похожим.

При работе с наблюдаемым старайтесь не:

  • переназначить наблюдаемую стоимость
  • использовать 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

person maxime1992    schedule 24.11.2020