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
Related
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 have two files 1) app.js 2) worker.js
I try to update the $scope.time but it is not showing in the view. It is my first time with webworkers.
app.js
angular.module('App', [])
.controller('MainCtrl', ['$scope', '$window', function($scope, $window) {
$scope.time = 100;
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
$scope.time = e.data.time;
};
worker.postMessage($scope.time);
}]);
worker.js
self.onmessage = function(e) {
var time = e.data;
var timer = setInterval(toDo,1000);
function toDo(){
time = time-1;
postMessage({
time:time
});
}
}
When worker.onmessage is triggered it is going to be outside the Angular digest cycle. So even though you have updated the model, Angular does not know that it needs to update the views. In order for you to notify Angular that a new digest cycle has to happen you need to call $scope.$apply()
worker.onmessage = function(e) {
$scope.$apply(function(){
//do model changes here
$scope.time = e.data.time;
});
};
Instead of passing an anonymous function to $scope.$apply you could just do the changes and then call $scope.$apply() with no arguments. But I believe it is preferred that you use the anonymous function with $apply as it does under the hood work like wrapping it in try...catch.
$scope.time = e.data.time;
$scope.$apply();
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.
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);
};
});
I am currently playing about with the LastFM API and trying to get a Recently Played Tracks list to update as I play tracks through Spotify and ITunes. I have got the initial code working through a combination of JS and Handlebars so that a static list of tracks is loaded in on page load which is current at the time of page load.
However I want the list to update as I select a new track without refreshing the page. So I thought I could just use a setInterval function to call my original function every 5 seconds or so. However for some reason my setInterval function is only running once on page load.
I know that this is a real simple error but I can't work out why? Help!!
var clientname = {};
clientname.website = (function(){
var
initPlugins = function(){
var setupLastFM = (function(){
/* Create a cache object */
var cache = new LastFMCache(),
/* Create a LastFM object */
lastfm = new LastFM({
apiKey : '6db1989bd348bf91797bad802c6645d8',
apiSecret : '155270f02728b1936ed7699e9f7b8de9',
cache : cache
}),
attachTemplate = function(data, handlebarsTemplateID){
var template = Handlebars.compile(handlebarsTemplateID.html());
$(".container").append(template(data));
}
/* Load some artist info. */
lastfm.user.getRecentTracks({user: 'jimmersjukebox'}, {
success: function(data){
var trackData = data.recenttracks.track,
tracks = $.map(trackData, function(track) {
if(track['#attr']){
var isCurrentTrack = true;
}
return {
currenttrack: isCurrentTrack,
song: track.name,
artist: track.artist['#text']
};
});
attachTemplate(tracks, $("#trackInfo"));
}, error: function(code, message){
}}),
intervalID = window.setInterval(console.log("test"), 1000);
}());
}
return{
init: function(){
initPlugins();
}
};
})();
$(window).load(clientname.website.init);
You are running console.log("test") immediately. Try encapsulating this in anther function, but do not instantiate it by including the parenthesis ().
intervalID = window.setInterval(function(){
console.log("test");
}, 1000);
You should not call the function in setInterval. It needs a callback.
Say like bellow
intervalID = window.setInterval(function(){
console.log("test");
}, 1000);
I would recommend to use setTimeout: Use a function to contain the setTimeout, and call it within the function:
$(function() {
var current = $('#counter').text();
var endvalue = 50;
function timeoutVersion() {
if (current === endvalue) {return false;} else {
current++;
$('#counter').text(current);
}
setTimeout(timeoutVersion, 50);
}
$('a').click(function() {
timeoutVersion();
})
})
JS Fiddle
You used a function call instead of a function reference as the first parameter of the setInterval. Do it like this:
function test() {
console.log("test");
}
intervalID= window.setInterval(test, 1000);
or you can do this also:
intervalID= window.setInterval( function() {
console.log("test!");
}, 1000);