I am in a spot where I need to poll my server for data every so often. I have looked around at how people are handling this in angularjs and I am pretty confused.
Some examples are of just simple counters that increment up/down. Other are using the $timeout service. I need the ability to turn this on/off with a button click. I.E. click to start poll, poll every 30 seconds, click button to stop polling.
I am not claiming to be great at javascript nor angular so please go easy. I did write my own service that uses setInterval and clearInterval:
angular.module('myModule', [])
.factory('TimerService',
function () {
var timers = {};
var startTimer = function(name, interval, callback) {
// Stop the timer if its already running, no-op if not running
stopTimer(name);
timers[name] = setInterval(function() {
callback();
}, interval);
// Fire right away, interval will fire again in specified interval
callback();
}
var stopTimer = function(name) {
var timer = timers[name];
if (timer) {
clearInterval(timer);
delete timers[name];
}
}
return {
start: startTimer,
stop: stopTimer
};
});
Then in my controller I do this:
var timerARunning = false;
$scope.onClickA = function() {
var timerName = 'timerA';
timerARunning = !timerARunning;
if (timerARunning) {
TimerService.start(timerName, 5000, function() {
alert("Timer A just fired");
});
} else {
TimerService.stop(timerName);
}
}
Related
I've a requirement where as soon as i load a page it makes makes api call and
shows data on the page. and after every 15 sec it keeps loading the data an shows and i've a popup that has a click button function(ConfigModalButtonClick ). When i open the popup i wanted to stop the interval that makes the api call so for that i added $interval.cancel when popup is open but now when i click the button in popup the api call never happen. how can i make sure api call happens it looks like i need to call interval function again somewhere but not sure where??
the interval should run normally after 15 sec when i close the popup.
$scope.timeDelay = 15000;
$scope.loadData = function() {
$http.post(url,data
).then(
function(response) {
$scope.output = response.data;
},
function(response) {
$scope.retrieveErrorMssg = "Failed to retrieve required data. Try again.";
});
};
var interval = $interval(function() {
$scope.loadData();
}, $scope.timeDelay);
$scope.showConfigurationModal = function() {
$interval.cancel(interval);
$scope.state.settingModalVisible = true;
};
$scope.closeConfigurationModal = function() {
$scope.state.settingModalVisible = false;
// Need to explicitly trigger the dismiss function of the modal
$scope.settingModalDismiss();
};
$scope.ConfigModalButtonClick = function () {
$scope.state.settingModalVisible = false;
//some data manipulation
$scope.loadData();
};
I created a simple chat with the frontend in angularjs. It has on the left size a simple index, of all the conversations that user has, with a show(chat_id) action, that opens up on the right side the chat itself, using polling to fetch new messages.
The main piece of the controller that does the functionality described:
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
var poll = function(){
conversation.get(id).then( function( conversation ) {
$scope.chat = conversation.data.messages;
$scope.form = true;
$timeout(function() {poll()}, 5000)
});
}
poll();
}
My problem, is whenever I click on two different conversations quickly, say show(1) and show(2) I get a weird behavior where it switches from conversation 1 to 2, back and forth, with the polling action.
Here is the get requests angular is making.
and for contextualization, here's the chat simple UI
As I said in the comment, every time show() is called, you start a new recursive loop that polls the conversation with the given id every 5 seconds.
So, if you click 4 conversations (1, 2, 3 and 4), you will end up with a refresh of conversation 1 every 5 seconds, another refresh of conversation 2 every 5 seconds, etc.
That's not what you want. Once you show a conversation, you only want to refresh that conversation, not the other ones.
So you could use that following code that cancels the previous timeout every time show() is called, and that verifies that the displayed conversation ID is the right one when getting a response:
var timer;
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
if (timer) {
$timeout.cancel(timer);
}
var poll = function(){
conversation.get(id).then( function( conversation ) {
if ($scope.currentConversation == id) {
$scope.chat = conversation.data.messages;
$scope.form = true;
timer = $timeout(function() {poll()}, 5000);
}
});
}
poll();
}
You probably want to do a check inside your inner function to make sure that $scope.currentConversation === id. That'll prevent it from losing context. My guess is that it's a race condition that you're clicking before the first
conversation.get(id) comes back.
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
var poll = function(){
conversation.get(id).then( function( conversation ) {
if ($scope.currentConversation !== id) return; // Prevent the race condition
$scope.chat = conversation.data.messages;
$scope.form = true;
$timeout(function() {poll()}, 5000)
});
}
poll();
}
I have a few spots where things happen in the UI on a delay using $timeout or $interval. Here's a simplified example:
Controller code:
$timeout(function() {
$scope.showElement = true;
}, 10000);
HTML:
<div id="myElement" ng-show="showElement"></div>
I want to be able to create an end-to-end Protractor test that tests whether #myElement gets displayed after a 10 second wait. The only way I have found to do this is to call browser.sleep(10000), which results in an actual 10-second delay in my test. This works, but these pauses add up add up and significantly increase the duration of my tests. Imagine a situation where you wanted to test whether a modal pops up after 30 minutes of inactivity.
Is there a way to simulate the passage of a specific amount of time, similar to $timeout.flush() in a jasmine test?
You can decorate $timeout and $interval to override the delay supplied to them:
lower-wait-time.js
exports.module = function() {
angular.module('lowerWaitTimeDecorator', [])
.config(function($provide) {
$provide.decorator('$timeout', function($delegate) {
return function() {
// The second argument is the delay in ms
arguments[1] = arguments[1] / 10;
return $delegate.apply(this, arguments);
};
});
})
};
Usage
beforeAll(function() {
var lowerWaitTime = require('lower-wait-time');
browser.addMockModule('lowerWaitTimeDecorator', lowerWaitTime.module);
});
afterAll(function() {
browser.removeMockModule('lowerWaitTimeDecorator');
});
it('My-sped-up-test', function() {
});
You could do this potentially using async.whilst. The idea is keep on looking for the element until the timeout is reached. If the element is found BEFORE timeout reaches or if element is NOT found within the timeout, test fails otherwise it passes. I haven't tested this but you get the idea. For example,
var driver = browser.driver,
wd = browser.wd,
async = require('async'),
start = Date.now(),
found = false,
diff;
async.whilst(
function() {
var diff = Date.now() - start;
return diff <= 10000 && !found;
},
function(callback) {
driver.findElement(wd.By.id('myElement')).then(function() {
found = true;
callback();
},function(err) {
found = false;
callback();
});
},
function (err) {
var isTesrPassed = !err && found && diff>=10000;
assertTrue(isTestPassed, 'element visibility test failed');
}
);
i am trying to make an animation on an html table.
i use $interval to display each row one by one.
var loadList = function() {
Obj.query(function(obj){
$scope.objs = [];
$interval(function() {$scope.objs.push(obj.shift())}, 200, obj.length);
});
}
then there is a function to remove each row one by one,
and finally it's looping and reload the table again :
var cleanList = function() {
var delay = 200;
var n = $scope.objs.length;
if (n!==0) {
$interval(function() {$scope.objs.shift()}, delay, n);
}
$interval(function() { loadList() }, delay*n, 1);
}
loadList();
$interval(cleanList, 7000);
The code is working here (here is the plunker), but i guess there is a way to do something nicer with a kind of "callback" or "promise" to trigger when the cleanList function is completed ?
How can i do that ?
$interval returns a promise so you can simply call then() on it.
For example:
$interval(function() {$scope.objs.push(obj.shift())}, 200, obj.length).then(cleanList);
Here's a working plunkr example:
http://plnkr.co/edit/rYzXjM?p=preview
I am using ckEditor and have a directive containing:
ck.on('key', function () {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
});
});
This picks up every change to mode and key click inside the editor and updates the model.
However I notice that these changes are slowing down data entry. Is there a way that I could still catch every keypress but not have so many updates to the model. I was thinking of some kind of a timeout but I am not sure how I could implement that.
Here is an alternative method that doesn't use intervals. Each time a key is pressed, start a 1s timeout and after that perform the update. If a key is pressed more often than 1s, making it too fast, just reset the timer. You can use the same timer and same update function for other events too, making this nice an scalable.
function update() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
});
}
var timeout = 1000, timer;
ck.on('key', function () {
clearTimeout(timer);
timer = setTimeout(function() {
update();
}, timeout);
});
Alternatively, if you want to update every n seconds when a key is pressed, you could do something like this.
var timeout = 1000, timer, updating = false;
function update() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
updating = false;
});
}
ck.on('key', function () {
if (updating === true) return;
updating = true;
timer = setTimeout(function() {
update();
}, timeout);
});
However, if you need to check if content has changed and not if a key is pressed, you could use ck.checkDirty() and ck.resetDirty() to do the checking combined with an interval. There is also a change event, but I haven't tested it. There are many different content changes that do not trigger the key event, such as bolding some text, adding image or HR via icon, changing table properties, and dragging an image for example.
One way of doing this would be to wrap a set interval in a set timeout on keydown, which would allow you to keep updating and applying, but only if they've typed recently.
Something like this
var x = setInterval(function() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 200);
ck.on('key', function () {
if (!x) {
x = setInterval(function() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 200);
};
setTimeout(function() {
window.clearInterval(x), 2000
});
});
However, I haven't tested this code, but if you want to put up a fiddle with the situation you're describing, I can do some testing.
Edit: Just realised previous method would create intervals for each callback scope.