15

I am willing to do a countdown timer in Angular 2 that start from 60 (i.e 59, 58,57, etc...)

For that I have the following:

constructor(){
  Observable.timer(0,1000).subscribe(timer=>{
  this.counter = timer;
});
}

The above, ticks every second, which is fine; however, it goes in an ascending order to an unlimited number. I am not sure if there is a way to tweak it so I can have a countdown timer.

Murhaf Sousli
  • 10,769
  • 18
  • 103
  • 170
Folky.H
  • 1,052
  • 4
  • 14
  • 29

2 Answers2

35

There are many ways to achieve this, a basic example is to use the take operator

import { Observable, timer } from 'rxjs';
import { take, map } from 'rxjs/operators';

@Component({
   selector: 'my-app',
   template: `<h2>{{counter$ | async}}</h2>`
})
export class App {
   counter$: Observable<number>;
   count = 60;

   constructor() {
     this.counter$ = timer(0,1000).pipe(
       take(this.count),
       map(() => --this.count)
     );
   }
}

A better way is to create a counter directive!

import { Directive, Input, Output, EventEmitter, OnChanges, OnDestroy } from '@angular/core';

import { Subject, Observable, Subscription, timer } from 'rxjs';
import { switchMap, take, tap } from 'rxjs/operators';

@Directive({
  selector: '[counter]'
})
export class CounterDirective implements OnChanges, OnDestroy {

  private _counterSource$ = new Subject<any>();
  private _subscription = Subscription.EMPTY;

  @Input() counter: number;
  @Input() interval: number;
  @Output() value = new EventEmitter<number>();

  constructor() {

    this._subscription = this._counterSource$.pipe(
      switchMap(({ interval, count }) =>
        timer(0, interval).pipe(
          take(count),
          tap(() => this.value.emit(--count))
        )
      )
    ).subscribe();
  }

  ngOnChanges() {
    this._counterSource$.next({ count: this.counter, interval: this.interval });
  }

  ngOnDestroy() {
    this._subscription.unsubscribe();
  }

}

Usage:

<ng-container [counter]="60" [interval]="1000" (value)="count = $event">
  <span> {{ count }} </span>
</ng-container>

Here is a live stackblitz

Murhaf Sousli
  • 10,769
  • 18
  • 103
  • 170
  • 1
    Mutating external state from within a composed observable is rarely a good idea and here it's unnecessary. An alternative: `Observable.timer(0, 1000).map(value => 60 - value).takeWhile(value => value > 0)` – cartant Jun 12 '17 at 03:49
  • @cartant this isn't a state. it is just a countdown timer – Murhaf Sousli Jun 12 '17 at 18:18
  • how can i achieve this when the counter hits to 0 it will recount again to 10. i mean in my in display. i use to display the number 10 and will start the countdown to 9 ,8 ,7 ,6 ,5 ,4.. and so on until it hits 0. and when it hits to zero it will start the countdown again to 10. – Jydon Mah Nov 20 '17 at 06:07
  • 1
    @JydonMah there is a `repeat()` operator that can be used at the end of the chain – Murhaf Sousli Nov 20 '17 at 15:06
  • 1
    thanks for this man, i add it in the end of the code but it continue to negative value. can you provide working example for it? it seems im having trouble figuring out. by the way im just new in angular and typescript :) – Jydon Mah Nov 21 '17 at 03:49
  • @MurhafSousli Is there some built-in property or something alternative I could use to have minutes and seconds? like MM:SS countdown? – Murlidhar Fichadia Jan 16 '18 at 06:54
  • Can I achieve this without implement

    {{countDown | async}}

    in html?
    – IntoTheDeep Jan 19 '18 at 08:54
  • @MurlidharFichadia you can make pipe that will transform seconds into minutes – karoluS Aug 29 '18 at 08:16
  • How do check, if the counter has reached 0? – Shrinivas Jul 06 '20 at 19:32
  • @Shrinivas Add operator to check the count `map(() => if (this.count === 0))` – Murhaf Sousli Jul 07 '20 at 01:39
2

Import into Component:

import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/timer";
import "rxjs/add/operator/finally";
import "rxjs/add/operator/takeUntil";
import "rxjs/add/operator/map";

Function CountDown:

countdown: number;
 startCountdownTimer() {
    const interval = 1000;
    const duration = 10 * 1000;
    const stream$ = Observable.timer(0, interval)
      .finally(() => console.log("All done!"))
      .takeUntil(Observable.timer(duration + interval))
      .map(value => duration - value * interval);
    stream$.subscribe(value => this.countdown = value);
  }

Html :

<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Countdown timer</h2>
  </div>
  <div class="panel-body">
    <div>
      <label>value: </label> {{countdown}}
    </div>
    <div>
      <button (click)="startCountdownTimer()" class="btn btn-success">Start</button>
    </div>
  </div>
</div>
Mohammad Daliri
  • 1,090
  • 5
  • 15
  • 37