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.
Related
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 ?
I am curious to know which one is better in terms of performance;
Multiple instance of listener who listen to an event, or a singleton to handle that listening process and then return only the value for everybody else to use?
Example:
function one(){
$window.on('scroll',function(e){
doOne(e.scrollValue);
}
}
function two(){
$window.on('scroll',function(e){
doTwo(e.scrollValue);
}
}
function three(){
$window.on('scroll',function(e){
doThree(e.scrollValue);
}
}
or
var getScrollValue = (function(){
var _scrollValue = 0;
$window.on('scroll',function(e){
_scrollValue = e.scrollValue;
})
return function() {
return _scrollValue;
}
})();
doOne(getScrollValue());
doTwo(getScrollValue());
doThree(getScrollValue());
May I suggest you to take a look at my personal workaround about this :
angular.module('myApp',[])
.controller('mainCtrl',function($window, $scope){
$scope.scrollPos = 0;
$window.onscroll = function(){
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
$scope.$apply(); //or simply $scope.$digest();
/* IMPORTANT NOTE :
* As $apply() is actully calling $rootScope.$digest() you can
* directly use $scope.$digest() instead of $scope.$apply()
* for better performance depending on context.
* Long story short : $apply() will always work but force
* the dirty checking of all scopes that may cause
* perfomance issue.
*/
};
});
working JSFiddle here
Here I'm not calling any function but you can easily do it in the onscroll one depending on $scope.scrollPos value.
I think it's a better practice to not set too many listeners.
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
I have a controller in which a value gets randomly generated
app.controller('detailReadingCtrl',function(){
var value = 0;
$scope.dispValue = 0;
setInterval(function(){
value = Math.floor(Math.random()*1000);
$scope.dispValue = value;
}
});
and my html is
<div>{{dispValue}}</div>
The Value on the html is not getting updated with the changed value. The dispvalue is changing in the controller but it is not updating in the html. I want to see the value change for every second on the screen without the need of refresh.
I tried using $scope.$watch and also $scope.$broadcast both seem to not work. Please let me know if you have any ideas about this.
setInterval doesn't trigger a $digest cycle, instead, use the $interval module:
app.controller('detailReadingCtrl',function($scope, $interval){
var value = 0;
$scope.dispValue = 0;
$interval(function(){
value = Math.floor(Math.random()*1000);
$scope.dispValue = value;
});
});
And you had other errors in the code ($scope wasn't being injected, missing ) at the end of the interval)
I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.