Reputation: 2305
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
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
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
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