I'm trying to count up a variable every x seconds in JS using setInterval() and show it in my view binding this variable the Angular way. The problem is, in the model the var is counted up but the progress is just shown as soon as I stop the Interval. How can I update the var in the view on every tick?
<span>{{number}}</span>
and:
$scope.number = 0;
$scope.interval;
$scope.run = function(){
$scope.interval = setInterval(function(){
$scope.number++;
}, 1000);
};
$scope.stop = function(){
clearInterval($scope.interval);
}
Fiddle
You should be using Angular's implementation of setInterval called $interval.
Not only will this will ensure any code within the callback calls a digest, but it will also help you easily test your code:
$scope.run = function() {
$scope.interval = $interval(function() {
$scope.number++;
}, 1000);
};
$scope.stop = function() {
$interval.cancel($scope.interval);
};
I would also avoid attaching your interval variable to the $scope. I can't see any reason your view would need to be aware of it. A private var interval in the controller scope would suffice.
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function ($scope, $interval) {
$scope.number = 0;
$scope.run = function (){
$scope.interval = $interval(function(){
$scope.number++;
}, 1000);
};
$scope.stop = function() {
$interval.cancel($scope.interval);
};
});
Related
angularjs code
var app = angular.module("testApp", []);
app.controller('captureCtrl', function () {
var vm = this;
vm.obj = {
show: false
};
addressControls.control.listen('populate', function (address, variations) {
vm.line1 = address.Line1;
vm.line2 = address.Line2;
vm.city = address.City;
vm.postcode = address.PostalCode;
vm.obj.show = true;
});
vm.test = function () {
vm.obj.show = true;
}
});
vm.obj.show value in the view doesn't get updated when changed inside the 'populate' event, but it does get updated inside vm.test function. How can I get this to work and why won't binding get updated in the view? I'm using angularjs 1.6.
Try $scope.apply().
$scope.$apply(function () {
//Your Code here
});
EDIT 1: I'm sorry--I completely overlooked something. You're using "this" instead of "$scope".
EDIT 2: Since it's a non-angular function, you definitely need a $scope.$apply() in there.
Try doing this instead:
var app = angular.module("testApp", []);
app.controller('captureCtrl', function ($scope) {
$scope.obj = {
show: false
};
addressControls.control.listen('populate', function (address, variations) {
$scope.line1 = address.Line1;
$scope.line2 = address.Line2;
$scope.city = address.City;
$scope.postcode = address.PostalCode;
$scope.obj.show = true;
$scope.$apply();
});
$scope.test = function () {
$scope.obj.show = true;
};
});
In general, use "$scope" if you want to expose properties for your DOM to work with.
I am trying to reload my models every 5000 milliseconds for which I am using the AngularJS $interval function to invoke my init () method. I want to stop reloading the models after all the values in a list are "COMPLETED" or no value in a given list is either Processing. Any Clue how to achieve this ?
function DeliveriesController(deliveriesService, $interval){
var vm = this;
vm.defaultWorkspace = 'HOT_POT';
vm.currentWorkspace = vm.defaultWorkspace;
vm.priorities = []; // priorities are based the names of each workspace.
// So call the workspace end point from the workspaceService
// to get a list of all workspace. Then assign it to the priorities.
vm.deliveries = {};
vm.selectTab = selectTab;
vm.retryDelivery = retryDelivery;
vm.removeDelivery = removeDelivery;
vm.downloadLog = downloadLog;
vm.getDeliveryDdex = getDeliveryDdex;
vm.refresh = refresh;
$interval(init, 5000);
return init();
/**
* Get the list of deliveries and initialize the model
*/
function init(){
deliveriesService.getDeliveries(vm.currentWorkspace).then(function (responseValues){
vm.deliveries = responseValues;
});
}
function refresh(){
init();
}
function selectTab(workspace){
vm.currentWorkspace = workspace;
init();
}
var myInterval = $interval(init, 5000);
some condition is met:
$interval.cancel(myInterval);
I am trying to create an Angular service which uses a web worker to change countdown variable using set interval.
What I want to do is to show the count down in the view.
I can easily do this by putting all the code in controller, which works but I got struck in creating the service
I am struck. I dont know how to proceed.
I have tried this plunkr here
script.js
angular.module('app', []).
controller('mainCtrl', mainCtrl);
function mainCtrl($scope,timer) {
$scope.time = 100;
console.log(timer.timeValue.time);
}
mainCtrl.$inject = ['$scope','timer'];
timer.js
angular.module('app')
.service('timer', timer);
function timer() {
var time;
this.timeValue = function(value) {
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
//console.log('From Main:'+ e.data.time);
time = e.data.time;
};
worker.postMessage(time);
return time;
};
}
worker.js
angular.module('app')
.service('timer', timer);
function timer() {
var time;
this.timeValue = function(value) {
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
//console.log('From Main:'+ e.data.time);
time = e.data.time;
};
worker.postMessage(time);
return time;
};
}
What I want to do is like this. This is my earlier plunk.This do the same thing using controller.
plunkr here
I found out why it's not working with your code. Just for the record, a countdown is not something you want to do with a Webworker, but anyway!
First of all in timer.js:
angular.module('app')
.service('timer', timer);
timer.$inject=['$rootScope']
function timer($rootScope) {
this.timeValue = function(value) {
var time = value;
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
time = e.data.time;
$rootScope.$broadcast('timerUpdate', time)
};
worker.postMessage(time);
};
}
You have to start the var time with a value.
I injected $rootScope to the service, so i can $broadcast a message back to the main scope.
In the main script I did this:
function mainCtrl($scope,timer) {
function init() {
timer.timeValue(100);
}
$scope.time = 100;
$scope.$on('timerUpdate', function(event, time) {
$scope.$apply(function() {
$scope.time = time;
})
})
init();
}
mainCtrl.$inject = ['$scope','timer'];
So, i made a Init function that gets triggered once in the beginning. That triggers your service into making a webworker.
Once the webworker gives back the message(time). The timerService sends out a $rootScope.$broadcast picked up by $scope.$on().
The $scope.$apply is not really the best thing to have in a simple script like this, but it's the only thing that will force digest(Angular page update) the page and give the $scope.time a new value.
and last the webworker:
self.onmessage = function(e) {
var time = e.data;
var timer = setInterval(toDo, 1000);
function toDo() {
time--;
postMessage({
time: time
});
}
}
(Only thing i did was change time = time - 1 to time--; (shorthand version, looks beter !)
Hope this helps !
(also, just for the record, try no to use the $rootScope or the $scope.$apply function! It's not the best way to do stuff I hear, but I'm also new to Angular and haven't found anything beter for these things..)
And the plunker:
https://plnkr.co/edit/7IoGxFaaqQRH4AErGenl?p=preview
I have different pages on may application which have their own controllers. One of them has an $interval function, let's say a timer. Click on a button will start this interval function, which updates itself every second. What i want to have is, i want to be able to go to any other page in my application (calling different controllers), but i want my interval to continue running until i stop it explicitly from the first controller. A rootScope interval so to speak. How can i do it?
EDIT: Thanks to Chris and Patrick i now have a simple Service, looks like this:
.service('TimerService', function($interval) {
var promise;
var timerSeconds = 0;
this.start = function () {
promise = $interval(function () {
timerSeconds++;
}, 1000);
};
this.stop = function () {
promise.cancel(interval);
timerSeconds = 0;
};
this.getTimer = function() {
return timerSeconds;
}
})
I store also my current value (timerSeconds) in this service. But how can i sync this value to my controller? The service increments the timerSeconds, and at the beginning of my controller i read it from this service through its getTimer() function, but it clearly will not be updated on my controller. How can i sync this service attribute with my local attribute?
EDIT:
when i define my service attribute as an object and the timerSeconds as number inside that object (it seems primitives cannot be synced):
var timer = {seconds : 0};
this.getTimer = function() {
return timer;
}
and get this object from my controller through that getter:
vm.timer = TimerService.getTimer();
they are all in sync.
Don't bother adding it to $rootScope. Use a service that can be used anywhere in the app. Here is a singleton timer that can start and stop. Define the intervalTimeout in the service itself, or if you want to be really flexible, do so in a provider (probably overkill for this example).
angular.module('myApp', [])
.service('AppCallback', function ($interval) {
var states = states = {
PENDING: 0,
STARTED: 1
}, intervalTimeout = 3000, // Set this
interval;
this.states = states;
this.state = states.PENDING;
this.start = function (callback) {
if (this.state !== states.PENDING) {
return;
}
interval = $interval(callback, intervalTimeout);
this.state = states.STARTED;
}
this.stop = function () {
if (this.state !== states.STARTED) {
return;
}
$interval.cancel(interval);
this.state = states.PENDING;
};
})
.controller('MainController', function ($scope, AppCallback) {
var vm = {},
count = 0;
vm.toggle = function toggle() {
if (AppCallback.state === AppCallback.states.PENDING) {
AppCallback.start(function () {
vm.data = 'Ticked ' + (++count) + ' times.';
});
} else {
AppCallback.stop();
}
};
$scope.vm = vm;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainController">
{{vm.data}}
<br />
<button ng-click="vm.toggle()">Toggle</button>
</div>
If you want to share any data between controllers the correct way is to use a service.
I would then create a service that allows you to stop and start this timer / interval.
The initial controller would kick this off and it would continue to "tick" forever until it is stopped.
Ok. I have spent several hours trying in vain to get Karma to work with my Angular controller. Whatever I do, I get the following error. It seems that even if I remove the expectGET() calls, I still get the error; as soon as I call $http.flush();
TypeError: Cannot set property 'totalBeforeDiscounts' of undefined
The code for my controller is as follows:
var quotePadControllers = angular.module('quotePadControllers', []);
quotePadControllers.controller('QuotesController', ['$scope', '$http', '$q', function($scope, $http, $q){
var blankAddon;
// Setup initial state and default values
var ajaxGetAddOns = $http.get('/?ajax=dbase&where=aons'),
ajaxGetFrames = $http.get('/?ajax=dbase&where=fcats');
$q.all([ajaxGetAddOns, ajaxGetFrames]).then(function(results){
$scope.addons = results[0].data;
$scope.frames = results[1].data;
$scope.pairs = [
{
"frames" : angular.copy($scope.frames),
"addons" : angular.copy($scope.addons),
}
];
});
// Function for the 'add pair' button
$scope.addPair = function()
{
$scope.pairs.push({
"frames" : angular.copy($scope.frames),
"addons" : angular.copy($scope.addons)
});
};
// Function for the 'remove pair' button
$scope.removePair = function()
{
if ( $scope.pairs.length > 1 )
{
$scope.pairs.pop();
}
};
// Continually update the subtotal and total
$scope.$watch('pairs', function(pairs) {
var totalBeforeDiscounts = 0;
angular.forEach(pairs, function(pair) {
var subTotal = 0;
angular.forEach(pair.addons, function(addon) {
subTotal += addon.added ? addon.price : 0;
});
subTotal += pair.currentFrame !== undefined ? pair.currentFrame.price : 0;
pair.subTotal = subTotal;
totalBeforeDiscounts += subTotal;
});
pairs.totalBeforeDiscounts = totalBeforeDiscounts;
}, true);
}]);
and my test code:
describe('QuotesController', function()
{
beforeEach(module('quotePadApp'));
var ctrl, $scope, $http, frameCatsHandler, addOnsHandler, createController;
// Setup tests
beforeEach(inject(function($controller, $rootScope, $httpBackend, _$q_) {
$scope = $rootScope.$new();
$http = $httpBackend;
frameCatsResponse = [{"id":145,"price":25,"brand":"mybrand"},
{"id":147,"price":45,"brand":"mybrand"},
{"id":148,"price":69,"brand":"mybrand"}];
addOnsHandler = [{"id":1,"name":"addon1","price":30,"includeIn241":0,"description":null},
{"id":2,"name":"addon2","price":60,"includeIn241":0,"description":null}];
frameCatsHandler = $http.when('GET', '/?ajax=dbase&where=fcats').respond(frameCatsResponse);
addOnsHandler = $http.when('GET', '/?ajax=dbase&where=aons').respond(addOnsHandler);
createController = function()
{
return $controller('QuotesController', {'$scope' : $scope });
};
}));
it('Should request frame cats and addons from the database', function()
{
$http.expectGET('/?ajax=dbase&where=aons');
$http.expectGET('/?ajax=dbase&where=fcats');
createController();
$http.flush();
});
});
This is because you have the following watch statement in your controller trying to set a totalBeforeDiscounts property on $scope.pairs.
$scope.$watch('pairs', function(pairs) {
// ...
pairs.totalBeforeDiscounts = totalBeforeDiscounts;
}, true);
In your tests, when you create the controller and then call $http.flush(), that's actually triggering a $digest cycle. This kicks off all watchers.
createController();
$http.flush();
The watch handler above will execute and since it executes before $scope.pairs has any value, the pairs argument passed into the watch handler is undefined, resulting in your error.
As per the documentation:
After a watcher is registered with the scope, the listener fn is
called asynchronously (via $evalAsync) to initialize the watcher. In
rare cases, this is undesirable because the listener is called when
the result of watchExpression didn't change. To detect this scenario
within the listener fn, you can compare the newVal and oldVal. If
these two values are identical (===) then the listener was called due
to initialization.
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Also, in the rest of your code you have $scope.pairs as an array, but in the watch you are trying to set a property like totalBeforeDiscounts on it. This doesn't look right.