Angular timer custom countdown variable - javascript

I'm using angular-timer: http://siddii.github.io/angular-timer/
My goal is to create a timer for an app that keeps reference to a variable somewhere else. That way instead of having a timer that just restarts on page load I will have a timer that consistently counts down regardless of what the user does. Most examples with angular-timer have you enter a countdown number. Is there any way to pass in a variable like so:
var timeRemaining = 1000;
<h1 class="timer"><timer countdown=timeRemaining max-time-unit="'minute'" interval="1000">{{mminutes}} minute{{minutesS}}, {{sseconds}} second{{secondsS}}</timer></h1>
Instead of being forced to write the countdown like this:
countdown="1000"
I've already tried passing in the variable via the toString() method as well. Thanks.

It looks like you cannot do what you are trying to without editing the directive itself.
Alternatively, you could use the timer from this question on your scope, just modify it to count down: https://stackoverflow.com/a/12050481/4322479
Counting down, from the question's comments: http://jsfiddle.net/dpeaep/LQGE2/1/
function AlbumCtrl($scope,$timeout) {
$scope.counter = 5;
$scope.onTimeout = function(){
$scope.counter--;
if ($scope.counter > 0) {
mytimeout = $timeout($scope.onTimeout,1000);
}
else {
alert("Time is up!");
}
}
var mytimeout = $timeout($scope.onTimeout,1000);
$scope.reset= function(){
$scope.counter = 5;
mytimeout = $timeout($scope.onTimeout,1000);
}
}

No need to edit directive, you could also use ng-if on your timer element to check for your startTime variable.
<timer ng-if="yourCountdownVaariable>0" countdown="yourCountdownVaariable"
max-time-unit="'hour'" interval="60000">
{{hhours}} hour{{hoursS}}, {{mminutes}} minute{{minutesS}}
</timer>
This way, the directive will only be initialised when you have your date, and it will work. Original issue below:
https://github.com/siddii/angular-timer/issues/36

Related

angularjs services and controllers data out of sync

I've got what I thought was a fairly simple AngularJS application. I used to have a simple countdown timer in my controller code, but I decided to break it out into its own service. That's where the problems began.
Previously, when my timer code was embedded within the controller, the countdown scope variable displayed correctly - every second, it would count down one less, until 0, as per the timer function. However, now that I've moved this to a service, and been passing the data back and forth with some function calls, the countdownvariable counts down 2 numbers every second, rather than 1. If I console.log(countdown); in my service's rundownClock() function, the correct countdown number is displayed each pass, however: 10,9,8,7...to 1.
Can anyone figure out what I'm now doing wrong, and why this "double counting" is occurring? Am I not maintaining the scope correctly in the controller?
Here is some of the controller, with the relevant CountdownService bits highlighted:
myApp.controller('myCtrl', ['$scope', 'CountdownService', function($scope, CountdownService) {
// TIMER SERVICES
$scope.startTimer = CountdownService.startTimer;
$scope.runClock = function () {
$scope.updateCountdown();
if (($scope.countdown > 0) && ($scope.roundStarted == true)) {
CountdownService.rundownClock($scope.countdown);
}
};
$interval($scope.runClock, 1000);
$scope.updateCountdown = function () {
CountdownService.setCurrentRound($scope.currentRound);
$scope.countdown = CountdownService.getCountdown();
$scope.roundStarted = CountdownService.getRoundStarted();
}
}]);
Here's some of the service in question. (Don't worry about the rounds variable set-up at the beginning, it's not relevant to the problem):
myApp
.factory("CountdownService", function (gameSetUp) {
var rounds = gameSetUp.rounds,
roundStarted = false,
roundFinished = false,
currentRound = 0,
countdown = rounds[currentRound].time;
// grab the round from the controller's scope to set the current round
function setCurrentRound(round) {
currentRound = round;
}
function getRoundStarted() {
return roundStarted;
}
function getCountdown() {
return countdown;
}
function startTimer() {
roundStarted = true;
}
function rundownClock() {
if (roundStarted === true) {
if (countdown > 0) {
countdown = countdown - 1;
}
if (countdown === 0) {
roundFinished = true;
}
}
}
return {
startTimer: startTimer,
rundownClock: rundownClock,
getCountdown: getCountdown,
getRoundStarted: getRoundStarted,
setCurrentRound: setCurrentRound
};
});
And finally, a snippet from the view, where the countdown scope variable is displayed:
<div class="timer md-body-2">{{ countdown }} seconds</div>
Update #downvoter :
Here is a working demo ( without using controller in 2 places route and template)
Here is the exact behavior that the author is talking about (using controller in route and template)
My original answer
I think your myCtrl controller is running twice, so, your $interval($scope.runClock, 1000); is running twice also ...
Are using registering myCtrl as route controller and in your template with ng-controller ?

Can't render model's property

Asking for help with Angular.
Somewhy, cannot refresh property (timerValue) when its value is changed. It does render it once.
Here's html div:
<div>{{ game.timerValue }}</div>
The js:
// Game status
$scope.game = {
"started" : false,
"timerValue" : 60,
"score" : 0,
"question" : "? ? ?",
"message" : "If all options are set up, then you may start!",
"wrong" : ""
};
// Handle Start Button click
$scope.startGame = function () {
if($scope.game.timer) clearTimeout($scope.game.timer);
$scope.game.score = 0;
$scope.game.wrong = "";
$scope.game.message = "The game started!";
$scope.game.timer = setInterval(function() {
$scope.game.timerValue -= 1;
if( $scope.game.timerValue <= 0)
{
$scope.game.message = "Defeat! Time is out! Your score is " + $scope.game.score;
clearTimeout($scope.game.timer);
}
},1000);
};
Running out of ideas, thanks for any help.
Update: The property is changed, the timer is working. It is not refreshing.
The reason your UI is not updated is because your game timer logic runs outside the regular Angular digest cycle. There's a nice article explaining it: $watch How the $apply Runs a $digest.
Instead of using setInterval, it is recommended to use Angular's $interval service. It is a wrapper for window.setInterval and releases you from the duty of having to manually call $scope.$apply or "tell Angular to update the UI".
Additional benefits of using $interval:
It wraps your callback for you automatically in a try/catch block and let's you handle errors in the $exceptionHandler service.
It returns a promise and thus tends to interoperate better with other promise-based code than the traditional callback approach. When your callback returns, the value returned is used to resolved the promise.
An alternative solution would be to explicitly call $scope.$apply() inside setInterval to notify Angular that "model data has changed, update the UI".
you can do a $scope.$apply() at the end of each interval to get what you want to achieve. just be wary of confilicts if you try to do a $scope.$apply() any where inside this function (if you were to extend it) or outside, if you were to extend the function that calls this.
You could also do what #Discosultan suggested and use $interval, which should automatically apply changes to your view from the scope at the end of each interval and will not create conflicts if you use a $scope.$apply() elsewhere in your code. By using $interval it will become part of your digest cycle, and you want to make sure not to put to much computational heavy code inside your digest loop otherwise it could slow down your entire app, as explained below in the comments by #AlvinThompson
setInterval does its work in a separate thread (sort of), so Angular cannot detect any changes to properties it makes. You have to wrap any functions that modifies properties with $scope.$apply(function () {... so that Angular detects them and pushes those changes to the UI.
$scope.$apply();
Working JS Bin
$scope.game.timer = setInterval(function() {
$scope.game.timerValue -= 1;
if( $scope.game.timerValue <= 0)
{
$scope.game.message = "Defeat! Time is out! Your score is " + $scope.game.score;
clearTimeout($scope.game.timer);
}
$scope.$apply();
},1000);
You are refreshing with a function that is outside Angular (setInterval). To tell angular to apply the change in your view, you have two solutions :
using $scope.$apply() :
$scope.startGame = function () {
if($scope.game.timer) clearTimeout($scope.game.timer);
$scope.game.score = 0;
$scope.game.wrong = "";
$scope.game.message = "The game started!";
$scope.game.timer = setInterval(function() {
$scope.game.timerValue -= 1;
$scope.$apply();
if( $scope.game.timerValue <= 0)
{
$scope.game.message = "Defeat! Time is out! Your score is " + $scope.game.score;
clearTimeout($scope.game.timer);
}
},1000);
};
or using $timeout :
$scope.startGame = function () {
if($scope.game.timer) $timeout.cancel($scope.game.timer);
$scope.game.score = 0;
$scope.game.wrong = "";
$scope.game.message = "The game started!";
$scope.game.timer = $timeout(function() {
$scope.game.timerValue -= 1;
if( $scope.game.timerValue <= 0)
{
$scope.game.message = "Defeat! Time is out! Your score is " + $scope.game.score;
$timeout.cancel($scope.game.timer);
}
},1000);
};
Without forgetting to inject ̀$timeout in your controller dependencies.

AngularJS: determine time elapsed, use to regularly update model and view

Context
I'm looking to create a webapp that looks at a set of data as a function of time elapsed since pageload. Think "how many calories have you burnt since opening this webpage".
I'm still trying to wrap my head around AngularJS services, factories, etc, and was wondering what the best way to create an auto-updating Timer that could be used to regularly (per second) manipulate and update ng-model.
How I've (unsuccessfully) imagined it'd work:
I've got something like this at the moment:
app.factory('Timer', function($timeout) {
var time = 0;
var Timer = function() {
this.time++;
this.timeout = $timeout(this.Timer, 1000);
}
});
And use as
$timeout(function() {
$scope.someNgModelVarForTheView = Timer.time * $scope.data;
}, 1000);
But... well. In my mind that works beautifully. In reality screw all happens, and I'm kidding myself if I know the right way to do this...
So I suppose, two questions:
How do you calculate time since pageload, as a callable function?
How do you recalculate the data model on a regular (per second) basis? Is $timeout a good method?
If you want to have your own service, you could do it like this:
.factory('MyTimer', function($interval){
return function(delay){
var initialMs= (new Date()).getTime();
var result = {totalMilliseconds:0, counts:0};
$interval(function() {
result.totalMilliseconds = (new Date()).getTime() - initialMs;
result.counts++;
}, delay);
return result;
};
})
And you could use it like this:
.controller('testController', function($scope, MyTimer){
$scope.t = MyTimer(1000);
});
And in your html you could do this:
<div ng-app="app" ng-controller="testController">
Total Ms: {{t.totalMilliseconds}}
Counts: {{t.counts}}
</div>
Example
Eh, I ended up overcomplicating my thoughts. This did the trick:
var delay = 1000; // 1 sec
$scope.currentTime = 0;
$interval(function() {
$scope.currentTime += delay;
$scope.someData *= $scope.currentTime;
}, delay);

DOM changes don't execute in interval function

I'm working on a basic alerting system in javascript that makes a basic circle clock count down and then disappear on a map I'm making.
I have however run into a small snag since I can't figure out why line 11 and 12 in the code block below (marked by arrows) don't want to do their thing.
function createTimer(sysID){
this.IDstore = sysID;
this.time = 59;
var self = this;
document.getElementById('star'+self.IDstore).style.fill="#FF0000";
this.timer = setInterval(function(){
document.getElementById('Timer'+self.IDstore).setAttribute('d', updateState(self.time));
self.time-=1;
if(self.time<=0){
-> document.getElementById('Timer'+self.IDstore).style.visibility="hidden";
-> document.getElementById('star'+self.IDstore).style.fill="#FFFFFF";
clearInterval(self.timer);
}
},1000);
this.stopTime=stopTime;
}
If I however remove the clearInterval or move them out of the if statement they work fine. But simply making them run by themself before the execution of the clearInterval like in the next code block does not work.
function createTimer(sysID){
this.IDstore = sysID;
this.time = 59;
var self = this;
document.getElementById('star'+self.IDstore).style.fill="#FF0000";
this.timer = setInterval(function(){
if(self.time>=0){
document.getElementById('Timer'+self.IDstore).setAttribute('d', updateState(self.time));
}else if(self.time>-2){
document.getElementById('Timer'+self.IDstore).style.visibility="hidden";
document.getElementById('star'+self.IDstore).style.fill="#FFFFFF";
}else{
clearInterval(self.timer);
}
self.time-=1;
},1000);
this.stopTime=stopTime;
}
Would be great to know why and if there is a simple solution.

stopping dynamically generated setInterval

I am generating multiple charts each with their own setInterval to refresh the data. I have it set to clearInterval when the dynamically generated container is removed - but if I reload and it has the same id the old setInterval continues to run. Is there a way to set a dynamically named setInterval that can be stopped when the replacement is generated?
Right now I'm using:
function generateChart(data, location){
var chart = new Highcharts.Chart({
// blah blah blah
}, function(chart){
setInterval(function(){
if($('#'+location).length){
// I'm doing stuff every minute
}else{
clearInterval();
}
},60000);
});
}
What happens is, the location is a randomly generated string that becomes the element ID for the container for the Highchart and if they user saves the chart it becomes the unique identifier. If the user updates the chart that's saved and reloads the chart, the old one gets .removed() and the new one generated in its place. Now the new one has the same element ID as the old one and since the old interval finds the container it wants it attempts to continue updating - which is can't since its chart went poof.
is there a way to set a dynamic variable I can use for setInterval so that I can clearInterval on it?
var blob+location = setInterval(function(){ ...
and then
clearInterval(blob+location);
You can just use an object:
var myObj = {};
var location = "somevalue";
myObj[location] = setInterval(...
clearInterval(myObj[location]);
ok - since I couldn't seem to wrap my head around some of your answers I decided to go low tech.
function genToken(){
var num = Math.floor(Math.random() * 10000);
var token = 't-' + num;
return token;
}
function genLocation(){
var chartToken = genToken();
var newChart = '<div id="'+location+'" data-token="'+chartToken+'"></div>';
$('#chartHome').append(newChart);
}
// inside my chart function
var token = $('#'+location).data('token');
setInterval(function(){
if( $('[data-token="'+token+'"]').length ){
// still there - keep going
}else{
// all gone - time to stop
clearInterval();
}
},60000);
now when I do:
$('#'+location).remove();
the token also vanishes and won't be the same if I generate a new chart with the same location id.
Stop using setInterval, use setTimeout instead (How do I execute a piece of code no more than every X minutes?):
function generateChart(data, location) {
var element = $('#'+location);
var chart = new Highcharts.Chart({
// blah blah blah
}, foo);
var foo = function() {
if(element){
// I'm doing stuff every minute
setTimeout(foo, 6000);
}
};
}
To stop it, just avoid the setTimeout or make element = null.
Maybe my code is a little bit wrong (I'm getting sleep right now), but the thing is to use setTimeout and closures.
If inside foo, something longs more than 6 seconds you will be in troubles since setTimeinterval will call it again, please watch http://www.youtube.com/watch?feature=player_detailpage&v=i_qE1iAmjFg#t=462s , so, this way you ensure that this will run 6 seconds after the last completed stuff.
I'll let this example here to posterity:
http://jsfiddle.net/coma/vECyv/2/
var closure = function(id) {
var n = 0;
var go = true;
$('#' + id).one('click', function(event) {
go = false;
});
var foo = function() {
if(go) {
console.log(id, n++);
setTimeout(foo, 1000);
}
};
foo();
};
closure('a');
closure('b');
Not sure if anyone is still looking for this solution but I ran into this problem and chose the following approach.
For anyone dynamically creating private/anonymous intervals that need to be stopped based on some event. You can simply save the interval in a variable, then transfer that variable into a data property in your html element.
// Outer scope
let pos = 1
let interval = setInterval(() => {
if (pos < 700) {
pos++;
}
htmlEl.style.top = pos + "px";
});
htmlEl.setAttribute("data-interval", interval)
This will save the numeric identifier of your interval, providing that html element is somewhere in your DOM.
Then, later you can simply extract this data attribute and use it to cancel an interval.
let intervalId = document.querySelector("#someElement").dataset.interval;
clearInterval(intervalId);

Categories

Resources