I'm new to programming, and have recently been playing around with AngularJS.
To practice, i've decided try and create a simple stopwatch.
Starting with an initial 'time' value of 0, i'm using $interval to increment the 'time' by 0.01, every 10 milliseconds. I can start and stop the stopwatch without any issues, UNLESS i click 'Start' twice. After doing so, 'Stop' no longer works.
I'm sure this is an awful way to create a stopwatch, but regardless, the issue still remains.
My html contains the timer and 'Start' and 'Stop' buttons, like so:
<div class="row" style="margin-left: 20px" ng-controller="timerCtrl">
<b>{{time | number : 2}}</b>
<button ng-click="startTimer()">Start</button>
<button ng-click="stopTimer()">Stop</button>
</div>
And the js:
.controller('timerCtrl', ['$scope', '$timeout', '$interval',
function($scope, $timeout, $interval) {
$scope.time = 0;
$scope.startTimer = function() {
$scope.counter = $interval(function(){
$scope.time += 0.01;
}, 10)
}
$scope.stopTimer = function() {
$interval.cancel($scope.counter);
}
}
])
What's the best way to solve this? Any help will be much appreciated, thanks!
The problem is that $interval returns a promise, and each time you run $scope.startTimer you are creating a new promise. When you run it a second time, it doesn't cancel the previous promise, it just re-assigns $scope.counter to the new promise.
Check out the AngularJS $interval page to see their example and method of avoiding this problem.
A simple solution is to check to see whether the var you're assigning your promise to has already been defined when you are about to create a new promise. For example:
if ( angular.isDefined($scope.counter) ) return;
You can also use a boolean var to maintain the status as to whether it has been started or not if you prefer that.
Related
I got this strange problem. For some bizzare reason, my setInterval won't work without $http.get() inside it. Currently, $http is set as a dependency for controller hosting said interval. But if I get rid of them (both dependency and call itself) setInterval stops working. I have no idea how to fix this.
Here is code for the controller
main.controller('timeCtrl', function($scope, $http, clockService) {
$scope.time = clockService.timeBase();
setInterval(function() {
$http.get();
$scope.time = clockService.timeBase();
}, 500);
});
After removing the dependancy, it looks like this
main.controller('timeCtrl', function($scope, clockService) {
$scope.time = clockService.timeBase();
setInterval(function() {
$scope.time = clockService.timeBase();
}, 500);
});
But it doesn't work. What the heck is wrong?
setInterval is JS function and it won't trigger digest cycle so angular won't see that something have changed.
try using it's angular version - $interval
P.S. when a promise is resolved it triggers digest cycle so that call to $http did the job, but it's obviously not the way to do what you want
Im working on angularjs 1.4. Im trying to have some frontend-cache collection that updates the view when new data is inserted. I have checked other answers from here Angularjs watch service object but I believe Im not overwriting the array, meaning that the reference is the same.
The code is quite simple:
(function(){
var appCtrl = function($scope, $timeout, SessionSvc){
$scope.sessions = {};
$scope.sessions.list = SessionSvc._cache;
// Simulate putting data asynchronously
setTimeout(function(){
console.log('something more triggered');
SessionSvc._cache.push({domain: "something more"});
}, 2000);
// Watch when service has been updated
$scope.$watch(function(){
console.log('Watching...');
return SessionSvc._cache;
}, function(){
console.log('Modified');
}, true);
};
var SessionSvc = function(){
this._cache = [{domain: 'something'}];
};
angular.module('AppModule', [])
.service('SessionSvc', SessionSvc)
.controller('appCtrl', appCtrl);
})();
I thought that the dirty checking would have to catch the changes without using any watcher. Still I put the watcher to check if anything gets executed once the setTimeout function is triggered. I just dont see that the change is detected.
Here is the jsbin. Im really not understanding sth or doing a really rockie mistake.
You need to put $scope.$apply(); at the bottom of your timeout to trigger an update. Alternatively you can use the injectable $timeout service instead of setTimeout and $apply will automatically get called.
jsbin
In this fiddle (http://jsfiddle.net/edwardtanguay/6pn8tb83/1), Angular increments a number every second, yet when an alert window pops up, the counting stops and when I close the alert window, I get the error
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.2.1/$rootScope/inprog?p0=%24apply
This error link actually takes me to a page which seems to be explaining what I need to do, but since I'm not explicitly using $scope.$apply() or $scope.$digest(), I don't understand what I need to do so that Angular simply continues to increment and show the incremented number while the alert window is popped up.
<div ng-controller="MyCtrl">
<div>Angular is counting: {{counter}}</div>
<button ng-click="processFiles()">processFiles</button>
<div>{{message}}</div>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope, $interval) {
var theTimer = $interval(function () {
$scope.counter++;
}, 1000);
$scope.counter = 0;
$scope.message = 'click button';
$scope.processFiles = function() {
alert('ok');
}
}
This is the default behaviour. Script execution stops when browser dialogues are open.
Workaround is to get the time difference between the alert and updating the counter with it, another one is to create your own modal
I am trying to implement the directive that is consuming the data from the service and reacts accordingly. However, something is going wrong and I need some assistance.
Here is the sample of the code that also can be found at http://jsfiddle.net/3c9h7/5/
var app = angular.module('myApp', []);
app.service('MyService', [
'$rootScope', function($rootScope) {
this.value = 4;
var self = this;
function inc(){
self.value += 3;
}
setInterval(inc, 1000);
}
]);
app.directive('myDir', ['MyService', function(MyService){
return {
link : function(scope, element){
function expr(){
return MyService.value;
}
function react(){
element.html(MyService.value);
}
scope.$watch(expr, react);
react();
}
}
}]);
<div ng-app='myApp'my-dir>
</div>
As the result Div element displaying the initial value of MyService.value but is ignoring the updates that happens in the service every second.
I have found the solution which involves rootScope(i.e. uncomment lines 7 and 9 in the jsFiddle sample):
function inc(){
$rootScope.$apply(function(){
self.value += 3;
});
}
setInterval(inc, 1000);
However, it does seem to be right to me..All the samples I found are not using this trick..So, am I missing something? Is "rootScope" solution appropriate? Maybe there is a better way to achieve the goal?
Thanks!
Use Angular's $interval (ref) instead of window.setInterval().
What it does though is actually calling apply() under the hoods, so your solution is correct, just a bit more complex. Also $interval can be mocked for testing.
I am simulating polling a server by manually changing db values, and allowing my controller to query the api every 10 seconds. Things work, however if the DB changes, then the value in the view switches to 0 before changing to the number it should be.
Controller
messageControllers.controller('NavigationCtrl', ['$scope', '$routeParams', 'Message', '$timeout',
function ($scope, $routeParams, Message, $timeout) {
var messages = Message.query({ type:'inbox' });
$scope.inbox = messages;
var poll = function() {
$timeout(function() {
var messages = Message.query({ type:'inbox' });
$scope.inbox = messages;
poll();
}, 10000);
};
poll();
}]);
Snippet from View
<li><a data-ng-href="#/">Inbox <span class="badge"><% (inbox|filter:{read:false}).length %></span></a></li>
e.g. if the number of read==false results is 5, then the db changes and now the number of read==false results is 6. What happens is instead of <% (inbox|filter:{read:false}).length %> changing straight to 6, it changes to 0 first.
Just getting to grips with Angular, and don't really understand what I am doing so sorry if this is a dumb question!!!
I have a feeling that it has something to do with the bound value in (inbox|filter:{read:false}).length being empty whilst AngularJS is waiting for the data, however I have no idea how to change this so that $scope.inbox only changes once Message.query is complete.
Message.query({ type:'inbox' }); is an async request.
It will return a reference object, which is populated after the query is finished. From the docs:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
Handily, you can access the promise which is resolved when the query finishes using the $promise property.
So try changing your code inside the timeout function to the following:
var messages = Message.query({ type:'inbox' });
// Populate the scope only after the query has resolved.
messages.$promise.then(function(){
$scope.inbox = messages;
});
poll();