4

I want to keep track of time on how long it would take for a user to click a button. I have already solved this problem but would want a better solution if there's any. Here is what I have:

export class MainComponent implements OnInit {

    timer : number = 0;
    intervalId : number;

    constructor() {
      this.intervalId = setInterval(() => {
        this.timer++;
      }, 1000);
    }

    ngOnInit() {}

    buttonClick = function() {
    alert(this.timer);
    this.timer = 0;
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Emenpy
  • 143
  • 1
  • 11
  • 1
    `setInterval` is not accurate at all - there will be substantial jitter. Just take the current system-time and compute the difference from the start. – Dai May 17 '18 at 00:23
  • Possible duplicate of [How to create an accurate timer in javascript?](https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript) – Dai May 17 '18 at 00:25

3 Answers3

3

Use performance.now() for accurate time-stamps (or fallback to new Date().getTime()) and compute the difference in UI update callbacks (via setInterval). Don't use setInterval itself to compute time - you cannot assume that setInterval calls will actually be called precisely every 1000ms.

Note I also moved the timer logic to the ngOnInit function instead of the constructor.

export class MainComponent implements OnInit {

    private start: number = null;
    private uiTimerId: number = null;

    constructor() {
    }

    private updateUI(): void {

        let delta = performance.now() - this.start;
        this.someUIElement.textContent = delta.toFixed() + "ms";
    }

    ngOnInit() {

        this.start = parseFloat( window.localStorage.getItem( "timerStart" ) );
        if( !this.start ) {
            this.start = performance.now();
            window.localStorage.setItem( "timerStart", this.start );
        }

        this.uiTimerId = window.setInterval( this.updateUI.bind(this), 100 ); // 100ms UI updates, not 1000ms to reduce UI jitter
    }

    buttonClick = function() {
        if( this.uiTimerId != null ) {
            window.clearInterval( this.uiTimerId );
            window.localStorage.removeItem( "timerStart" );
        }
    }
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • What if a user refresh the page? I want the timer to continue and now resetting the timer. – Emenpy May 17 '18 at 00:45
  • @Emenpy If you're referring to an "F5" refresh, then use `window.localStorage` to persist values between page-refreshes (in this case, you'd persist the `start` value). If it's just the `Component` refreshing in Angular then create an Angular Service that represents the stopwatch. – Dai May 17 '18 at 01:29
  • Can you show me an example of how to use window.localstorage for this case? – Emenpy May 17 '18 at 01:35
  • @Emenpy I've amended my answer to demonstrate how to use `localStorage` to persist the `start` value. – Dai May 17 '18 at 02:05
  • Why use an interval at all? – Zachscs May 17 '18 at 03:42
  • @Zachscs To update the UI to show the stopwatch's time value changing - it just isn't being used to actually calculate the time by adding the intervals together (which is just _wrong_). – Dai May 17 '18 at 05:03
  • update the UI? Where did you get that requirement from. All he had in his code was an alert with the value when the button is clicked. – Zachscs May 17 '18 at 05:05
  • After refreshing (Pressing F5), I get a negative value. – Emenpy May 17 '18 at 17:38
  • It looks like the performance.now() value get reset once the page refreshed. – Emenpy May 17 '18 at 18:03
1

Hey first of all we declare our member functions a bit differently in typescript, so buttonClick should look like this

buttonClick() {
  alert(this.timer);
  this.timer = 0;
}

as mentioned in the comment by @Dai, getting systemtime at start (at ngOnInit) and subtracting that from the system time on click will require far fewer operations and be more accurate.

ngOnInit() {
  this.startTime = localStorage.startTime ? JSON.parse(localStorage.startTime) : (new Date().getTime());
  localStorage.setItem('startTime', JSON.stringify(this.startTime));
}

buttonClick() {
  this.startTime = JSON.parse(localStorage.startTime);
  alert((this.startTime - (new Date().getTime())) / 1000);
}

EDIT: I edited the answer to show you have to use localStorage to persist values. This is similar to the answer above, however using idomatic typescript. I imagine the previous answer has lots of es5 experience and is resorting to those methods (nothing wrong with that). I find this style easier and clearer. I would reccomend taking an angular tutorial. Try the tour of heroes on their website and use Visual Studio code with Angular Essentials plugin as that will Lint and format your code properly so you can become accustomed to idiomatic typescript. Cheers.

Zachscs
  • 3,353
  • 7
  • 27
  • 52
0

Instead of an increment timer, you can code as below. The increment of the timer would be changed when the tab is inactive I have created a stopwatch in the most efficient way. You can move one tab to another tab. The time counting value will not change. You can reset, pause and play. You can visit the link for details.

  seconds: string = "00";
  minutes: string = "00";
  hours: string = "00";
   timer(){
    let mcountercal = 0;
    let currentSeconds = parseInt(this.seconds);
    let currentMinutes = parseInt(this.minutes);
    let currentHours = parseInt(this.hours);
    this.counter = currentHours * 3600000 + currentMinutes * 60000 + currentSeconds * 1000
    const startTime = Date.now() - (this.counter || 0);
    this.timeoutId = setInterval(() => {
      this.counter = Date.now() - startTime;
      currentHours = Math.floor(this.counter / 3600000);
      currentMinutes = Math.floor(this.counter / 60000) - currentHours * 60;
      mcountercal = Math.floor(this.counter / 60000);
      currentSeconds = Math.floor(this.counter / 1000) - mcountercal * 60;     
      this.hours = this.getFormattedTimeStamp(currentHours.toString());
      this.minutes = this.getFormattedTimeStamp(currentMinutes.toString())
      this.seconds = this.getFormattedTimeStamp(currentSeconds.toString())
}

getFormattedTimeStamp(timestamp:any) {
  return timestamp < 10 ? "0" + timestamp : timestamp;
}

https://stackblitz.com/github/Ashraf111/StopWatchWhenTabActiveAndInactive