Angular Timeout calling function without delay - angularjs

In the following snippet, the progress call is getting triggered continuously with out the 10s delay. Please let me know where i am going wrong
ctrl.progress =function (fileName){
if(ctrl.status < 100){
ctrl.timer = $timeout(function(fileName){
LookUpValueService.getImportStatus(fileName).
then(function(value){
ctrl.status = value;
ctrl.progress(fileName);
});
//ctrl.status = ctrl.status + 1;
}(fileName),5000);
}
else{
$timeout.cancel(ctrl.timer);
}
}; ctrl.progress("test.txt");

The first argument to $timeout() has to be a function itself, not the result of a call to a function.
In your case, ctrl.progress() does not return a function, it returns undefined, so when you have:
ctrl.timer = $timeout(ctrl.progress(fileName), 10000);
what happens is that ctrl.progress(fileName) gets called immediately, returns undefined, and then you tell $timeout() to wait 10 seconds before doing undefined.
Change that line to use a function instead, and it will work:
ctrl.timer = $timeout(() => ctrl.progress(fileName), 10000);
(or if not using ES6/2015/TypeScript: ctrl.timer = $timeout(function () { ctrl.progress(fileName) }, 10000);)

Related

Angular $timeout calls function only once

I am using AngulaJs and Ionic.
I am trying to call a function every 3 seconds for 10 times only (this is why I am not using $interval). Thing is, the code below calls the function only once (while the debugging code to the console is called 10 times).
for (i = 0; i < 10; i++)
{
$timeout(function () {
$scope.play(); // this is the called function
console.log("progress bar: " + i);
}, 3000);
}
Any help will be appreciated,
Thanks in advance,
Pagugim
For loop will start 10 timeouts all together. But seems like you want them to execute one after another. For this case you can use recursion.
var count = 0;
replay();
function replay() {
$timeout(function () {
if(count < 10) {
$scope.play();
count++;
replay(); // call this function again
console.log("progress bar: " + $scope.progressVal);
}
}, 3000);
}
I am trying to call a function every 3 seconds for 10 times only
You can use the optional count argument of $interval.
var i=0;
function reportProgress() {
$scope.play(); // this is the called function
console.log("progress bar: " + i);
i++;
};
$interval(reportProgress, 3000, 10);
From the Docs:
$interval
Usage
$interval(fn, delay, [count], [invokeApply], [Pass]);
count (optional) Number of times to repeat. If not set, or 0, will repeat
indefinitely. (default: 0)
-- AngularJS $interval API Reference
Actually, the $scope.play() is called 10 times, but at almost the same time. To call the function every 3 seconds, you can use a closure to keep the value i and set timeout as 3000*i. I think this is what you want:
for (i = 0; i < 10; i++) {
(function(i) {
$timeout(function() {
$scope.play();
console.log("progress bar: " + i)
}, 3000 * i);
})(i);
}
Jsfiddle: https://jsfiddle.net/ealonwang/ens9wu0g/15/. Watch the value and console log change every 3 seconds.

Using _.each and $q promise to iterate widgets

I have a pretty straight-forward problem where I'm :
Iterating through a series of dashboard "widgets" using _.each().
Calling a function to refresh the current widget, and returning a $q promise.
Now, my issue is that I would like each iteration to WAIT prior to continuing to the next iteration.
My first version was this, until I realized that I need to wait for updateWidget() to complete:
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData);
}
});
My second version is this one, which returns a promise. But of course, I still have the problem where the iteration continues without waiting :
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData).then(function(data){
var i = 1;
});
}
});
and the called function which returns a deferred.promise object (then makes a service call for widget data) :
function updateWidget(widget, parWidData) {
var deferred = $q.defer();
// SAVE THIS WIDGET TO BE REFRESHED FOR THE then() SECTION BELOW
$rootScope.refreshingWidget = widget;
// .. SOME OTHER VAR INITIALIZATION HERE...
var url = gadgetDataService.prepareAggregationRequest(cubeVectors, aggrFunc, typeName, orderBy, numOrderBy, top, filterExpr, having, drillDown);
return gadgetDataService.sendAggGetRequest(url).then(function (data) {
var data = data.data[0];
var widget = {};
if ($rootScope.refreshingWidget) {
widget = $rootScope.refreshingWidget;
}
// BUILD KENDO CHART OPTIONS
var chartOptions = chartsOptionsService.buildKendoChartOptions(data, widget);
// create neOptions object, then use jquery extend()
var newOptions = {};
$.extend(newOptions, widget.dataModelOptions, chartOptions);
widget.dataModelOptions = newOptions;
deferred.resolve(data);
});
return deferred.promise;
}
I would appreciate your ideas on how to "pause" on each iteration, and continue once the called function has completed.
thank you,
Bob
******* UPDATED ************
My latest version of the iteration code include $q.all() as follows :
// CREATE ARRAY OF PROMISES !!
var promises = [];
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
promises.push(updateWidget(wid, parentWidgetData));
}
});
$q.all(promises)
.then(function () {
$timeout(function () {
// without a brief timeout, not all Kendo charts will properly refresh.
$rootScope.$broadcast('childWidgetsRefreshed');
}, 100);
});
By chaining promises
The easiest is the following:
var queue = $q.when();
_.each(widgets, function (wid) {
queue = queue.then(function() {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
});
});
queue.then(function() {
// all completed sequentially
});
Note: at the end, queue will resolve with the return value of the last iteration
If you write a lot of async functions like this, it might be useful to wrap it into a utility function:
function eachAsync(collection, cbAsync) {
var queue = $q.when();
_.each(collection, function(item, index) {
queue = queue.then(function() {
return cbAsync(item, index);
});
});
return queue;
}
// ...
eachAsync(widgets, function(wid) {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
}).then(function() {
// all widgets updated sequentially
// still resolved with the last iteration
});
These functions build a chain of promises in the "preprocessing" phase, so your callback is invoked sequentially. There are other ways to do it, some of them are more efficient and use less memory, but this solution is the simplest.
By delayed iteration
This method will hide the return value even of the last iteration, and will not build the full promise chain beforehands. The drawback is that, it can be only used on array like objects.
function eachAsync(array, cbAsync) {
var index = 0;
function next() {
var current = index++;
if (current < array.length) {
return $q.when(cbAsync(array[current], current), next);
}
// else return undefined
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
This will iterate over any iterable:
function eachAsync(iterable, cbAsync) {
var iterator = iterable[Symbol.iterator]();
function next() {
var iteration = iterator.next();
if (!iteration.done) {
// we do not know the index!
return $q.when(cbAsync(iteration.value), next);
} else {
// the .value of the last iteration treated as final
// return value
return iteration.value;
}
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
Keep in mind that these methods will behave differently when the collection changes during iteration. The promise chaining methods basically build a snapshot of the collection at the moment it starts iteration (the individual values are stored in the closures of the chained callback functions), while the latter does not.
Instead of trying to resolve each promise in your _.each(), I would build out an array of promises in your _.each to get an array like:
promises = [gadgetDataService.sendAggGetRequest(url1), gadgetDataService.sendAggGetRequest(url2)....]
Then resolve them all at once, iterate through the results and set your models:
$q.all(promises).then(function(results){ // iterate through results here })

$scope.$watch callback executes after a delay

I have the following:
$scope.$watch('duration.dayPreference',function(value){
console.log(value);
if(value=='every')
{
that.duration.days = 1;
}
else if(value=='selected')
{
//alert('test');
that.duration.days=[];
}
else if(value=='everyday')
{
that.duration.days='everyday';
}
});
this.selectDay = function (day) {
$scope.duration.dayPreference = 'selected';
//$scope.$apply();
/*if(typeof(this.duration.days)!='object')
{
this.duration.days=[];
}*/
var index = this.duration.days.indexOf(day);
if (index == -1) {
//alert('test2');
this.duration.days.push(day);
}
else {
this.duration.days.splice(index, 1);
}
}
In this, when I do $scope.duration.dayPreference = 'selected'; I expect the line below it to have the this.duration.days set to a blank array. But it doesn't. Upon a closer inspection, I found that the callback in the $watch runs after the line below the assignment.
It may be very probable that, $watch may be using some kinda timers internally. What should be the way to do it then.
The watch won't be triggered until the digest is run. This will be after your entire function is compete.
If you consider that AngularJS is itself written in JavaScript, there would be no way for it to react to your setting of a property at the time. You are using the thread yourself. It can only wait for you to finish and then react.
As for what to do instead...
Perhaps you could call that watch function manually?
Or maybe the code which expects the empty array should belong inside the watch?
Watch will trigger on the $digest, which will occur after current cycle/code finishes running. You need to figure out a way of rearranging your code that handles things asynchronously. One possible quick solution might be:
var selectedDays = [];
$scope.$watch('duration.dayPreference',function(value){
console.log(value);
if(value=='every')
{
that.duration.days = 1;
}
else if(value=='selected')
{
//alert('test');
that.duration.days = selectedDays;
}
else if(value=='everyday')
{
that.duration.days='everyday';
}
});
this.selectDay = function (day) {
$scope.duration.dayPreference = 'selected';
var index = selectedDays.indexOf(day);
if (index == -1) {
//alert('test2');
selectedDays.push(day);
}
else {
selectedDays.splice(index, 1);
}
}

getAttribute return unresolved

I'm just trying to save the attribute of an input value to a variable.
This is the code:
var sliderNumber = element.all(by.model('color.red')).get(1);
var firstNum = sliderNumber.getAttribute('value').then(function(value) {
return value;
});
//Some code that changes the sliderNumber attribute
expect(sliderNumber.getAttribute('value')).toEqual(firstNum + 1);
This gives me an error like this:
Expected '184' to equal Promise::433 ([[PromiseStatus]]: "pending")1.
I've also tried:
var firstNum = function() {
return sliderNumber.getAttribute('value').then(function(value) {
return value;
});
}
That didn't help at all. How do I resolve this promise?
Since .getAttribute() returns a promise, which will invoke your callback asynchronously, you need to put your test logic within the callback:
var sliderNumber = element.all(by.model('color.red')).get(1);
// get the initial value
sliderNumber.getAttribute('value').then(function(value) {
// once we've got the initial value, store it, then proceed with your test
var initialValue = parseInt(value, 10);
//Some code that changes the sliderNumber attribute
expect(sliderNumber.getAttribute('value')).toEqual((initialValue + 1).toString());
});
You can't simply get the return value outside of the callback, because that callback may or may not be called after the rest of your code.

How to avoid Infinite $digest Loop error while working with ngResource

I am $watching the $scope.randomObjects in my directive. The $watch will throw an error if the function is not stable and I think that my curried function inside getDesiredAmountOfObjects is idempotent. Is here something going on with ngResource -objects that I can not see?
How can I fix this? Also the desiredAmount is fixed for now.
Here is snippet from my controller:
..
var getDesiredAmountOfObjects = function (objects, randomObjects) {
return function (desiredAmount) {
var amount = desiredAmount || 1;
if (amount >= objects.length) {
return objects;
}
var randoms = randomObjects.slice(0, amount);
//logged objects are always the same in each $digest loop.
//including the $$hashKey
console.log(random);
return randoms;
};
};
//this will initialized only once in controller
ObjectRes.query(function(data) {
$scope.objects = data;
var randomObjects = [];
angular.extend(randomObjects, data);
randomObjects.sort(function () {
return 0.5 - Math.random();
});
$scope.randomObjects = getDesiredAmountOfObjects($scope.objects, randomObjects);
});
..
I can't see everything because I haven't seen how the $watch and views are set up, but something jumps out at me.
If randomObjects is being called from within a watcher, and set to a value on the scope which is also watched, it will cause an infinite $digest cycle.
This is because you are constantly returning a new array reference (slice returns a new array). The watcher of the result will recognize that the reference changed, which will require a new loop of the digest, which will ask for new randomObjects which will trigger that something changed, etc.
Usually, you fix this by making sure your function returns references to the same object, and does not create new arrays. In your case, you are calling slice which creates a new array every time. You need to make sure this function returns the same array every time.
Something like this, perhaps?
var getDesiredAmountOfObjects = function (objects, randomObjects) {
var result = [];
return function (desiredAmount) {
var amount = desiredAmount || 1;
if (amount >= objects.length) {
return objects;
}
// clear the result array and put randoms into it
result.length = 0;
result.push.apply(result, randomObjects.slice(0, amount));
//logged objects are always the same in each $digest loop.
//including the $$hashKey
console.log(result);
return result;
};
};

Resources