3

var app = angular.module('quizApp', []);

app.directive('timer', function($timeout, $compile) {
  return {
    restrict: 'E',
    replace: false,
    scope: {
      interval: '=interval',
      startTimeAttr: '=startTime',
      countdownattr: '=countdown'
    },
    controller: function($scope, $element) {
      if ($element.html().trim().length === 0) {
        $element.append($compile('<span>{{millis}}</span>')($scope));
      }

      $scope.startTime = null;
      $scope.timeoutId = null;
      $scope.countdown = $scope.countdownattr && parseInt($scope.countdownattr, 10) > 0 ? parseInt($scope.countdownattr, 10) : undefined;
      $scope.isRunning = false;

      $scope.$on('timer-start', function() {
        $scope.start();
      });

      $scope.$on('timer-resume', function() {
        $scope.resume();
      });

      $scope.$on('timer-stop', function() {
        $scope.stop();
      });

      function resetTimeout() {
        if ($scope.timeoutId) {
          clearTimeout($scope.timeoutId);
        }
      }

      $scope.start = $element[0].start = function() {
        $scope.startTime = $scope.startTimeAttr ? new Date($scope.startTimeAttr) : new Date();
        resetTimeout();
        tick();
      };

      $scope.resume = $element[0].resume = function() {
        resetTimeout();
        $scope.startTime = new Date() - ($scope.stoppedTime - $scope.startTime);
        tick();
      };

      $scope.stop = $element[0].stop = function() {
        $scope.stoppedTime = new Date();
        resetTimeout();
        $scope.timeoutId = null;
      };

      $element.bind('$destroy', function() {
        resetTimeout();
      });

      var tick = function() {
        if ($scope.countdown > 0) {
          $scope.countdown--;
        } else if ($scope.countdown <= 0) {
          $scope.stop();
        }

        $scope.millis = new Date() - $scope.startTime;

        if ($scope.countdown > 0) {
          $scope.millis = $scope.countdown * 1000
        }

        $scope.seconds = Math.floor(($scope.millis / 1000) % 60);
        $scope.minutes = Math.floor((($scope.millis / (1000 * 60)) % 60));
        $scope.hours = Math.floor((($scope.millis / (1000 * 60 * 60)) % 24));
        $scope.days = Math.floor((($scope.millis / (1000 * 60 * 60)) / 24));

        //We are not using $timeout for a reason. Please read here - https://github.com/siddii/angular-timer/pull/5

        console.log($scope.seconds)
        $scope.timeoutId = setTimeout(function() {
          tick();
          $scope.$apply();
        }, $scope.interval);

        $scope.$emit('timer-tick', {
          timeoutId: $scope.timeoutId,
          millis: $scope.millis
        });
      };

      $scope.start();
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="quizApp">
  <timer interval="1000">Time ends in : {{hours}} hours, {{minutes}} minutes, {{seconds}} seconds</timer>
</div>

I have this timer directive and got some issues listed below. Couldn't able to figure it out. Any help would be appreciated.

1) hours, minutes and seconds are not getting updated in markup.

2) timer is in increasing order. I want it in decreasing order.

I'm using this timer in my quiz app. So let's say total time is 1 hr 54mins 00sec. On starting quiz timer should start decreasing by seconds and then minutes and then hours. When it becomes 0, I should get an alert.

Thank you in advance.

1 Answers1

1

A few issues I see:

  1. You have two non-optional attributes defined in your directive scope, but they aren't being passed in in the timer directive HTML.
  2. The reason your HTML values aren't being populated is that those values aren't tied to the directive scope. You've set an isolate scope when you passed through those attributes to the directive. If they are tied to the directive, I'd add them as a template.
  3. There's no way to define where the timer starts. This is a good candidate for passing in a variable.
  4. Using controller function instead of link link vs. controller
  5. I wasn't sure what the millisecond code was doing, so I just displayed it on the counter if it exists. Feel free to make that however you wanted it.
  6. I wasn't sure what the $emit was doing, but I'm assuming some other code is using it. If not, you could remove that.
  7. Switched to using interval from timeout, because it created a ton of timeouts, but never stopped them. For example, each loop makes a new timeoutId, so if you try to stop it, it just stops the latest loop.

var app = angular.module('app', []);

app.directive('timer', function($timeout, $compile) {
  return {
restrict: 'E',
scope: {
  interval: '=', //don't need to write word again, if property name matches HTML attribute name
  startTimeAttr: '=?startTime', //a question mark makes it optional
  countdownAttr: '=?countdown' //what unit?
},
template: '<div><p>'+
  '<p>Time ends in : {{ hours }} hour<span data-ng-show="hours > 1">s</span>, ' +
  '{{ minutes }} minutes, ' +
  '{{ seconds }} seconds ' +
  '<span data-ng-if="millis">, milliseconds: {{millis}}</span></p>' +

  '<p>Interval ID: {{ intervalId  }}<br>' +
  'Start Time: {{ startTime | date:"mediumTime" }}<br>' +
  'Stopped Time: {{ stoppedTime || "Not stopped" }}</p>' +
  '</p>' +
  '<button data-ng-click="resume()" data-ng-disabled="!stoppedTime">Resume</button>' +
  '<button data-ng-click="stop()" data-ng-disabled="stoppedTime">Stop</button>',

link: function (scope, elem, attrs) {

  //Properties
  scope.startTime = scope.startTimeAttr ? new Date(scope.startTimeAttr) : new Date();
  var countdown = (scope.countdownAttr && parseInt(scope.countdownAttr, 10) > 0) ? parseInt(scope.countdownAttr, 10) : 60; //defaults to 60 seconds
  
  function tick () {
    
    //How many milliseconds have passed: current time - start time
    scope.millis = new Date() - scope.startTime;
    
    if (countdown > 0) {
      scope.millis = countdown * 1000;
      countdown--;
    } else if (countdown <= 0) {
      scope.stop();
      console.log('Your time is up!');
    }

    scope.seconds = Math.floor((scope.millis / 1000) % 60);
    scope.minutes = Math.floor(((scope.millis / (1000 * 60)) % 60));
    scope.hours = Math.floor(((scope.millis / (1000 * 60 * 60)) % 24));
    scope.days = Math.floor(((scope.millis / (1000 * 60 * 60)) / 24));

    //is this necessary? is there another piece of unposted code using this?
    scope.$emit('timer-tick', {
      intervalId: scope.intervalId,
      millis: scope.millis
    });
    
    scope.$apply();
    
  }
  
  function resetInterval () {
    if (scope.intervalId) {
      clearInterval(scope.intervalId);
      scope.intervalId = null;
    }        
  }
  
  scope.stop = function () {
    scope.stoppedTime = new Date();
    resetInterval();
  }
  
  //if not used anywhere, make it a regular function so you don't pollute the scope
  function start () {
    resetInterval();
    scope.intervalId = setInterval(tick, scope.interval);           
  }
  
  scope.resume = function () {
    scope.stoppedTime = null;
    scope.startTime = new Date() - (scope.stoppedTime - scope.startTime);
    start();
  }
  
  start(); //start timer automatically
  
  //Watches
  scope.$on('time-start', function () {
    start();
  });
  
  scope.$on('timer-resume', function() {
    scope.resume();
  });
  
  scope.$on('timer-stop', function() {
    scope.stop();
  });
  
  //Cleanup
  elem.on('$destroy', function () {
    resetInterval();
  });
  
}
  };
});
<!DOCTYPE html>
<html data-ng-app="app">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>

 <timer interval="1000" countdown="61"></timer>
 
</body>
</html>

Or working JSBin here.

intrepid_em
  • 518
  • 8
  • 28