Brian
Brian

Reputation: 2305

AngularJS Countdown within an ng-repeat

I have a small AngularJS app which searches and retrieves a listing of users and their next scheduled meeting (assuming one is scheduled in the next 8 hours) brought back from the server using JSON with the time format in UTC for ease of calculation to local times. Each user could have a different status (busy until x time, free until x time).

What I would like to accomplish is to be able to update the DOM with time remaining until the meeting scheduled has completed or time left until the meeting starts. I have some code working sort of, but because I am apparently not doing this correctly with only a few entries in the result set it brings the browser to a near standstill. Any suggestions would be appreciated!

My current code consists of the following snippets:

[Main Page]

<tr ng-repeat="item in pagedItems[currentPage-1] | orderBy:sortingOrder:reverse" ng-class="{success:item._freeBusy=='Free', error:item._freeBusy=='Busy'}">
       <td>{{item._firstName}}</td>
       <td>{{item._lastName}}</td>
       <td>{{item._facilityName}}</td>
       <td>{{item._extension}}</td>
       <td>{{item._description}}</td>
       <td><a ng-hide="!item._extension" ng-click="dial(item)">Dial</a></td>
       <td><button class="btn btn-primary" ng-click="openDetails(item)">Details</button></td>
       <td>{{item._freeBusy}} {{item._timeLeft}} {{calculateTime(item._freeBusyTime,$index)}}</td>
 </tr>

[Controller]

        $scope.timeUntil = function(s) {
        function isoToObj(s) {
            var b = s.split(/[-T+:]/i);
            return new Date(Date.UTC(b[0], --b[1], b[2], b[3], b[4], b[5]));
        }
        // Utility to add leading zero
        function z(n) {
            return (n < 10 ? '0' : '') + n;
        }
        // Convert string to date object
        var d = isoToObj(s);
        var diff = d - new Date();
        // Allow for previous times
        var sign = diff < 0 ? '-' : '';
        diff = Math.abs(diff);
        // Get time components
        var hours = diff / 3.6e6 | 0;
        var mins = diff % 3.6e6 / 6e4 | 0;
        var secs = Math.round(diff % 6e4 / 1e3);
        // Return formatted string
        return sign + z(hours) + ' Hours ' + z(mins) + ' Min' + ':' + z(secs);// + ':' + z(secs)
    }

    $scope.calculateTime = function(s, idx) {
        timeoutID = $timeout(function() {
            $scope.items[idx]._timeLeft = $scope.timeUntil(s);
            $scope.calculateTime(s, idx);
        }, 1000);
    };

EDIT

I understand the issues as mentioned below, what I am struggling with is how to register this corretly. As it could be up to 15+ separate times updating to a single tick that's where I am getting lost.

Upvotes: 0

Views: 2340

Answers (3)

Joseph Oster
Joseph Oster

Reputation: 5545

You are calling $scope.calculateTime recursively! And you are modifying the list of items during the ng-repeat, which also causes and endless loop.

How about this: http://plnkr.co/edit/0JqK96irV4ETdWZYxO3P?p=preview

changed the html to refer to a separate array that doesn't affect ng-repeat:

<td>in {{_timeLeft[$index]}}</td>

which is updated as follows:

$scope._timeLeft = [];

var intervalID = window.setInterval(function() {
    for (var i=0; i<$scope.items.length; i++) {
      $scope._timeLeft[i] = $scope.timeUntil($scope.items[i]._freeBusyTime);
    }
    $scope.$apply();
}, 1000);

Note that $scope.$apply() is required to let Angular know that '_timeLeft' has been modified, which updates all references to it.

Upvotes: 1

Sylvain
Sylvain

Reputation: 19269

You are registering way more timeouts than you think. Every time angular renders your view, you register new timeout handlers. Add a counter of your callback and watch the count go:

$scope.called = 0;

$scope.calculateTime = function(s, idx) {
    timeoutID = $timeout(function() {
        $scope.items[idx]._timeLeft = $scope.timeUntil(s);
        $scope.calculateTime(s, idx);
        console.log(++$scope.called);
    }, 1000);
};

See this plunk where I reproduced the bug: http://plnkr.co/edit/qJ4zDl6gc5C7Edg0T0gB. Just run it and watch the counter.

Why do you want to update your _timeLeft in the rendering cycle? Why not do this:

$scope.called = 0;

setInterval(function() {
  angular.forEach($scope.items, function function_name(item) {
    item._timeLeft = $scope.timeUntil(item._freeBusyTime);
  });
  console.log(++$scope.called);
  $scope.$apply();
}, 1000);

See new plunk: http://plnkr.co/edit/rVJ6p4VXDQvt7rjT6eka

Upvotes: 1

Tosh
Tosh

Reputation: 36030

I do not think you should call $timeout every view update. Instead, you can count down time within your controller and let view show the timer.

http://plnkr.co/edit/OUVdJyllzmVmPC0FhqYF?p=preview

Upvotes: 0

Related Questions