18

I want to have a date countdown like this:

http://codepen.io/garethdweaver/pen/eNpWBb

but in angular 2, I found this plunkr that adds 1 to a number each 500 miliseconds:

https://plnkr.co/edit/pVMEbbGSzMwSBS4XEXJI?p=preview

this is the code:

import {Component,Input} from 'angular2/core';
import {Observable} from 'rxjs/Rx';

@Component({
    selector: 'my-app',
    template: `
      <div>
        {{message}}
      </div>
    `
})
export class AppComponent {   

  constructor() {
    Observable.interval(1000)
              .map((x) => x+1)
              .subscribe((x) => {
                this.message = x;
              }):
  }
}

But I want to have a date taking one second until reach 0.

Diego Vieira
  • 1,150
  • 2
  • 13
  • 31
Diego Unanue
  • 6,576
  • 8
  • 47
  • 67

6 Answers6

29
import { Component, NgOnInit, ElementRef, OnInit, OnDestroy } from 'angular2/core';
import { Observable, Subscription } from 'rxjs/Rx';

@Component({
    selector: 'my-app',
    template: `
  <div>
    {{message}}
  </div>
`
})
export class AppComponent implements OnInit, OnDestroy {

    private future: Date;
    private futureString: string;
    private counter$: Observable<number>;
    private subscription: Subscription;
    private message: string;

    constructor(elm: ElementRef) {
        this.futureString = elm.nativeElement.getAttribute('inputDate');
    }

    dhms(t) {
        var days, hours, minutes, seconds;
        days = Math.floor(t / 86400);
        t -= days * 86400;
        hours = Math.floor(t / 3600) % 24;
        t -= hours * 3600;
        minutes = Math.floor(t / 60) % 60;
        t -= minutes * 60;
        seconds = t % 60;

        return [
            days + 'd',
            hours + 'h',
            minutes + 'm',
            seconds + 's'
        ].join(' ');
    }


    ngOnInit() {
        this.future = new Date(this.futureString);
        this.counter$ = Observable.interval(1000).map((x) => {
           return Math.floor((this.future.getTime() - new Date().getTime()) / 1000);
        });

        this.subscription = this.counter$.subscribe((x) => this.message = this.dhms(x));
    }

    ngOnDestroy(): void {
        this.subscription.unsubscribe();
    }
}

HTML:

 <my-app inputDate="January 1, 2018 12:00:00">Loading...</my-app>
KnowHoper
  • 4,352
  • 3
  • 39
  • 54
  • Yes, it works how can I send in inputdate a variable from the class I try like this but I get a null in the script. – Diego Unanue Apr 07 '16 at 18:17
  • You don't need attr. Just use [inputDate] has to be a string that parses to a date. My answer answered your original question, please can you mark it as correct? – KnowHoper Apr 07 '16 at 22:05
  • @HeySatan This works fine for me using a static date, but when I try to use a variable, I get this error: `Template parse errors: Can't bind to 'inputDate' since it isn't a known native property`. Any idea what causes it? Using Angular2 RC.1 – kolli Jun 01 '16 at 17:30
  • @DiegoUnanue Did this work for you using a variable? – kolli Jun 01 '16 at 17:30
  • 2
    @kolli here's a working plunkr I use input to get the variable from the parent https://plnkr.co/edit/9HZOKBZ3h2ZpQc5kgSfn?p=preview – Diego Unanue Jun 01 '16 at 19:24
  • @DiegoUnanue Unfortunately I couldn't get this to work with JS.. I added the countdown to the page directly now, will try again with a future Angular version... Thx anyway! – kolli Jun 02 '16 at 18:18
  • 1- Replace the countdown code with the one given in the plunkr in countdown.component.ts, 2- add a variable in the component where you added the countdown e.g. voteDate and 3- pass that variable to in inputDate (remember that the variable should have a date assigned). – Diego Unanue Jun 02 '16 at 19:27
  • 1
    @DiegoUnanue attached plunker is not working please update it ! – Pardeep Jain Oct 12 '16 at 06:27
  • 1
    @PardeepJain There you are... updated to angular 2 final release. https://plnkr.co/edit/SYwehoZvDciRnZkIygU5 Let me know if it works. – Diego Unanue Oct 12 '16 at 18:51
  • @HeySatan Great work! Can you just elucidate me why do we have to divide by 1000 the timestamp diff? Without that i get a year difference as hundred of thousand of days. Why is that? – FrancescoMussi Nov 23 '16 at 15:07
  • @johnnyfittizio getTime() returns the time in milliseconds, so dividing it by 1000 gives the diff in seconds. – KnowHoper Nov 24 '16 at 22:33
  • The convention with variables of type `Observable` is to suffix them with `$`, not prefix them. You can also choose not to add a suffix. – David Walschots May 20 '18 at 18:05
  • I have to say your usage of map in uncommon. Is there a reason to use it instead of just immediately subscribing and doing your operations there? You're not mapping the stream's value to something different... – David Walschots May 20 '18 at 18:57
6

This is a bit simplified version with hardcoded date and returning four different outputs (days - hours - minutes - seconds) that you can put nicely in four boxes:

export class HomePage {

    // Hardcoded date
    private eventDate: Date = new Date(2018, 9, 22);

    private diff: number;
    private countDownResult: number;
    private days: number;
    private hours: number;
    private minutes: number;
    private seconds: number;

    constructor(public navCtrl: NavController ) {

        Observable.interval(1000).map((x) => {
                this.diff = Math.floor((this.eventDate.getTime() - new Date().getTime()) / 1000);
            }).subscribe((x) => {           
                this.days = this.getDays(this.diff);
                this.hours = this.getHours(this.diff);
                this.minutes = this.getMinutes(this.diff);
                this.seconds = this.getSeconds(this.diff);
            });
    }

    getDays(t){
        return Math.floor(t / 86400);
    }

    getHours(t){
        const days = Math.floor(t / 86400);
        t -= days * 86400;
        const hours = Math.floor(t / 3600) % 24;
        return hours;
    }

    getMinutes(t){
        const days = Math.floor(t / 86400);
        t -= days * 86400;
        const hours = Math.floor(t / 3600) % 24;
        t -= hours * 3600;
        const minutes = Math.floor(t / 60) % 60;
        return minutes;
    }

    getSeconds(t){
        const days = Math.floor(t / 86400);
        t -= days * 86400;
        const hours = Math.floor(t / 3600) % 24;
        t -= hours * 3600;
        const minutes = Math.floor(t / 60) % 60;
        t -= minutes * 60;
        const seconds = t % 60;
        return seconds;
    }

}
FrancescoMussi
  • 20,760
  • 39
  • 126
  • 178
6

Made some few changes to Johnnyfittizio's answer. https://stackoverflow.com/a/40784539/4579897

import {Observable} from 'rxjs/Rx';
import {Component, OnInit} from '@angular/core';

export class TimerComponent implements OnInit {

    private _trialEndsAt;
    private _diff: number;
    private _days: number;
    private _hours: number;
    private _minutes: number;
    private _seconds: number;

    constructor() {}

    ngOnInit() {  
        this._trialEndsAt = "2017-02-28";
        Observable.interval(1000).map((x) => {
            this._diff = Date.parse(this._trialEndsAt) - Date.parse(new Date().toString());
        }).subscribe((x) => {           
            this._days = this.getDays(this._diff);
            this._hours = this.getHours(this._diff);
            this._minutes = this.getMinutes(this._diff);
            this._seconds = this.getSeconds(this._diff);
        });
    }

    getDays(t){
        return Math.floor( t/(1000*60*60*24) );
    }

    getHours(t){
        return Math.floor( (t/(1000*60*60)) % 24 );
    }

    getMinutes(t){
        return Math.floor( (t/1000/60) % 60 );
    }

    getSeconds(t){
        return Math.floor( (t/1000) % 60 );
    }

}
Shadow
  • 8,749
  • 4
  • 47
  • 57
Winnipass
  • 904
  • 11
  • 22
3

I thought it made more sense to create the timer as a Service so I can have more flexibility when creating my views (use the returned Time model however you want in your components). It creates a new observable upon subscription so each subscriber gets their own observable. In short, you can create as many timers at the same time with this service as you want. Be sure to include this service in your AppModule provider's array so you can use it throughout your application.

Service:

import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/defer';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';

export interface Time {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
}

@Injectable()
export class TimerService {

  constructor() { }

  private createTimeObject(date: Date): Time {

    const now = new Date().getTime();
    const distance = date.getTime() - now;

    let time: Time = {days: 0, hours: 0, minutes: 0, seconds: 0};
    time.days = Math.floor(distance / (1000 * 60 * 60 * 24));
    time.hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    time.minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    time.seconds = Math.floor((distance % (1000 * 60)) / 1000);
    return time;
  }

  timer(date: Date): Observable<Time> {
      return Observable.interval(1000)
        .map(() => this.createTimeObject(date))
  }
}

Now use it in a Component:

import {Component, OnInit} from '@angular/core';
import {Time, TimerService} from '../timer.service';

@Component({
  selector: 'app-timer',
  template: `
   <ng-container *ngIf="time1$ | async as time1"
     <pre>{{time1.days}}days {{time1.hours}}hours {{time1.minutes}} minutes {{time1.seconds}}seconds<pre>
     <br>
   <ng-container>

   <ng-container *ngIf="time2$ | async as time2"
     <pre>{{time2.days}}days {{time2.hours}}hours {{time2.minutes}} minutes {{time2.seconds}}seconds<pre>
     <br>
   <ng-container>
`
})
export class TimerComponent implements OnInit {

  time1$: Observable<Time>;
  time2$: Observable<Time>;

  constructor(private timerService: TimerService) {}

  ngOnInit() {

    this.time1$ = this.timerService.timer(new Date('June 4, 2020 15:37:25'))

    this.time2$ = this.timerService.timer(new Date('August 9, 2041 15:37:25'))

  }
Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76
tommyc38
  • 703
  • 1
  • 8
  • 21
3

UPDATED for RXJS 5.5 +

import {map} from 'rxjs/internal/operators';
import { Component, OnInit } from '@angular/core';
import {interval, Observable} from 'rxjs';

@Component({
  selector: 'app-countdown',
  templateUrl: './countdown.component.html',
  styleUrls: ['./countdown.component.css']
})
export class CountdownComponent implements OnInit {

  private _trialEndsAt;

  private _diff: number;
  private _days: number;
  private _hours: number;

  private _minutes: number;

  private _seconds: number;

  constructor() {}

  ngOnInit() {

      this._trialEndsAt = "2017-02-28";

      interval(3000).pipe(
        map((x) => {this._diff = Date.parse(this._trialEndsAt) - Date.parse(new Date().toString());
          })).subscribe((x) => {
              this._days = this.getDays(this._diff);
              this._hours = this.getHours(this._diff);
              this._minutes = this.getMinutes(this._diff);
              this._seconds = this.getSeconds(this._diff);
          });
  }

  getDays(t) {
      return Math.floor( t / (1000 * 60 * 60 * 24) );
  }

  getHours(t) {
      return Math.floor( (t / (1000 * 60 * 60)) % 24 );
  }

  getMinutes(t) {
      return Math.floor( (t / 1000 / 60) % 60 );
  }

  getSeconds(t) {
      return Math.floor( (t / 1000) % 60 );
  }

}
Vinez
  • 560
  • 2
  • 11
0

Here's a solution i came up with recently on my project. It checks between a gate opening time of an event and the main event time of the same event. This uses Momentjs.

I have code in the onChanges function in case an event time changes in my firebase which is very unlikely, but i like the fact it can pick up on it and update on the fly.

Component:

import { Component, OnInit, Input, OnChanges } from '@angular/core';
import { Observable } from "rxjs/Rx"
import { RoundService } from '../../round.service'
import * as moment from 'moment-timezone';

@Component({
  selector: 'race-countdown',
  templateUrl: './race-countdown.component.html',
  styleUrls: ['./race-countdown.component.scss']
})
export class RaceCountdownComponent implements OnChanges,OnInit{

    @Input() activeRound;

    public time:number;
    public timeToGates:number;
    public timeToMains:number;  
    public countDown:Observable<any>;
    public currentUnix = moment().unix();

    constructor(private rs:RoundService) { }

    ngOnChanges() {

    this.timeToGates = this.activeRound.gateOpenUnix - this.currentUnix;
    this.timeToMains = this.activeRound.mainEventUnix - this.currentUnix;

    if(this.timeToGates < 0)
        this.time = this.timeToMains
    else 
        this.time = this.timeToGates

  }

  ngOnInit() {

    this.countDown = Observable.interval(1000)
                                          .map(res=>{ return this.time = this.time-1 })
                                          .map(res=> {

                                            let timeLeft = moment.duration(res, 'seconds');
                                            let string:string = '';

                                            // Days.
                                            if(timeLeft.days() > 0) 
                                                string += timeLeft.days() + ' Days'

                                            // Hours.
                                            if(timeLeft.hours() > 0) 
                                                string += ' ' + timeLeft.hours() + ' Hours'

                                            // Minutes.
                                            if(timeLeft.minutes() > 0)
                                                string += ' ' + timeLeft.minutes() + ' Mins'

                                            // Seconds.
                                            string += ' ' + timeLeft.seconds(); 

                                            return string;

                                          })

  }

}

HTML:

<span>{{countDown | async}}</span>

Produces: '2 Days 6 Hours 59 Mins 42' etc

Ryann Galea
  • 307
  • 3
  • 18