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.
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 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'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'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 need a Factory object that can be applied with a $scope.property to get a result. I also need to know if either the modifier in the factory instance, or the $scope.property changes to update the result. How I am seeing this pattern might be wrong of course.
app.factory('MyFactory',
function () {
var MyFactory = function (name, modifier) {
this.name = name;
this.result = 0;
this.modifier = modifier;
}
//I might want to call this when modifier or MyProperty changes
MyFactory.prototype.modifyingMethod = function () {
this.result = this.modifier * //externalProperty;
}
MyFactory.prototype.watcher = function () {
//no idea what I will do here or if I need this at all
// I need to recalculate the result like with $watch
this.modifyingMethod();
}
return MyFactory;
}
)
app.controller('MyCtrl'
function($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = [
new MyFactory('Option1', 1),
new MyFactory('Option2', 2),
new MyFactory('Option3', 3),
new MyFactory('Option4', 4),
new MyFactory('Option5', 5)
];
}
So I have the problem that I need to $watch MyProperty and the modifier (it can be changed bu users) so I can change the result. If the Property is a value type passing it into the Factory constructor will not work. Perhaps I could pass a function in the returns MyProperty.
Can I set up internal $watch on the factory. If I would do this outside of the factory, in the controller, I would need to do it for each instance. Should I perhaps set up some sort of register method on my factory object?
Are there any suggestions, patterns or something I might want to use?
Basically you could understand your Factory as an interface to a collection of objects (either an array or associative array respectively pure javascript object).
I find your approach with Objects very appealing and I am sure you can achieve similar things. Still I put together a fiddle, that shows how I would solve the problem:
In a MVC pattern your Factory would be the model, the controller should be as simple as possible and your HTML with directives represents the view.
You controller watches for changes from the user ($scope.MyProperty with $watch). While the model is self aware of any depending external property changes. Note that the changes of the ExternalObject service/factory will only be recognizable, if those aren't primitive values. That is why in the ExternalObject factory I return the whole object.
In a perfect world you wouldn't need to listen for changes with an interval, but will receive a javascript event. If this is possible, do it! Note that object updates out of Angular's scope will need you to do a $scope.$apply(), if you want to see the changes in the view. $intervaland $timeout both call a $scope.apply(), so using them is best practice.
Actually there still has to be a lot of cleanup to be done in this code, but it might give you a basic idea how to use an alternative structure:
var app = angular.module('yourApp', []);
window.yourGlobalObject = {};
setInterval(function () {
yourGlobalObject.externalProperty = Math.floor(Math.random() * 5000);
}, 1000);
app.factory('ExternalObject', ['$window', function ($window) {
return $window.yourGlobalObject;
}]);
app.factory('MyFactory', ['$interval', 'ExternalObject', function ($interval, ExternalObject) {
var options = [{
name: 'Option1',
modifier: 1
}, {
name: 'Option2',
modifier: 2
}, {
name: 'Option3',
modifier: 3
}],
cachedExternalProperty = 0,
cachedMyProperty = 0;
MyFactory = {
getOptions: function () {
return options;
},
addOption: function (name, modifier) {
options.push({
name: name,
modifier: modifier
});
},
setMyProperty: function (value) {
cachedMyProperty = value;
},
setResults: function (myProperty) {
angular.forEach(options, function (option, key) {
option.result = option.modifier * ExternalObject.externalProperty * myProperty;
});
console.log(options);
}
};
// let the service check for updates in the external property, if changed
$interval(function () {
if (cachedExternalProperty !== ExternalObject.externalProperty) {
cachedExternalProperty = ExternalObject.externalProperty;
MyFactory.setResults(cachedMyProperty);
}
}, 1000);
return MyFactory;
}]);
app.controller('MyCtrl', ['$scope', 'MyFactory', function ($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = MyFactory.getOptions();
$scope.setResults = function () {
MyFactory.setResults($scope.MyProperty);
};
$scope.$watch('MyProperty', function (value) {
MyFactory.setMyProperty(value)
MyFactory.setResults(value);
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<section ng-app="yourApp" ng-controller="MyCtrl">
<button ng-click="setResults(MyProperty)">Update Results</button>
<div ng-repeat="factory in MyFactoryOptions">{{factory.name}} {{factory.result}}</div>
<input type="number" ng-model="MyProperty">
</section>
</body>