chaining multiple http calls against a loop - angularjs

I've found all kinds of useful posts/info about $q, $q.all, and chaining in general, but I haven't found any examples of my exact issue, so I'm wondering whether what I want to do is even possible. I've got a series of calls I need to make, and each is dependent on the previous BUT there's a step in the middle where the series splits into several parallels. The gist of it goes something like this:
call #1: get keyword (based on inputs)
call #2: with keyword, get list of items
call #3: with item ID#s, get item profiles
call #4: with data in profiles, get item images
(I know, I know, four calls for just one is ridiculous, let alone sixteen calls total, but this is for a prototype. I don't have to be ultra-speedy. I just need to prove I can get to that final step with the existing data/calls.)
Anyway, this means that #1 is one call for all... and then as of #2, it splits up and I have to do #2, #3, and #4 for each different ID# I got from #1's return. I've mocked up a plunk using jsonplaceholder. It should retrieve the first set, then do the next two calls with the retrieved IDs, one loop per ID.
I've tried a simple for loop (which Plunker tells me I can't do w/a function in it). I've tried an angular.forEach (inside a $scope.watch and outside it), but that doesn't seem to get me anything at all:
angular.forEach($scope.resultsb, function(value, key) {
console.log(key + ': ' + value);
$http.get('http://jsonplaceholder.typicode.com/users?id='+ value)
.then(function(res2){
$scope.data2 = res2.data;
var obj = {
title: $scope.results1[i].title,
id: $scope.data2[i].id,
username: $scope.data2.username
};
$scope.results2.push(obj);
I think the forEach just isn't kicking in properly b/c it can't really run until after I get $scope.results1 (the first step's results), but can I chain an $http call to a loop?
You can see the full thing (in its current version, which may change since I continue to beat at it) at http://plnkr.co/edit/CjXt7E?p=preview. Is what I'm trying even possible, or do I have to do this long-hand?

Your for each will run even without response as it is Asynchronous, instead make a function and call it when you get the callback in .then()
Edited your plunker: http://plnkr.co/edit/mxvvCB?p=preview
like this:
$scope.Call = function(value) {
if(typeof value[$scope.counter] !== 'undefined'){
value = value[$scope.counter].id;
var i =$scope.counter;
console.log(value+' ' +i);
$http.get('http://jsonplaceholder.typicode.com/users?id='+ value)
.then(function(res2){console.log(res2);
$scope.data2 = res2.data;
var obj = {
title: $scope.results1[i].title,
id: $scope.data2[0].id,
username: $scope.data2.username
};
$scope.results2.push(obj);
$scope.counter++;
$scope.Call($scope.resultsb);
});
}
};
I hope it is what you are trying to achieve.

I GOT IT TO WORK.
This might be the worst case of function-use ever, but it works, and I get results. Not entirely -- I think some of the calls time out, so I figured out I need to ask for, like, 6, and then I get at least the 4 results I want. Which is probably indicative of the fact that this desperately calls for a single endpoint instead of doing the logic on the frontend, but it works enough for a prototype, so I'm happy (for now).
In my controller, I get the first set of IDs, and using a simple for-loop, I assign a loop-id to each. Then I call getUser(), with num (to connect the info to the first set of results), and id (to connect to the actual user I'm tracking). Here's the function w/ its internal chains:
function getUser(num, id) {
$http.get('http://jsonplaceholder.typicode.com/users?id='+ id)
.then(function(res2){
$scope.data2 = res2.data;
if ($scope.data2[0].username !== 'undefined') {
var obj = {
num: $scope.results1[num].num,
id: $scope.results1[num].id,
title: $scope.results1[num].title,
username: $scope.data2[0].username
};
$scope.results2.push(obj);
}
return $http.get('http://jsonplaceholder.typicode.com/comments?id=' + id);
}).then(function(res3){
if ($scope.results2[num].username !== 'undefined') {
$scope.data3 = res3.data;
var obj = {
num: num,
id: $scope.results2[num].id,
title: $scope.results2[num].title,
username: $scope.results2[num].username,
email: $scope.data3[0].email
};
$scope.results3.push(obj);
}
});
}
You can see the entire thing at http://plnkr.co/edit/CjXt7E?p=preview -- it shows the output of each step (results1, results2, results3). Hopefully I won't have to do something this crazy again, but just in case, maybe this will help someone. Or maybe it'll cause someone to leap up and tell me everything I did wrong, but that'd be fine, too. Gotta learn somehow. :)

Related

Is there a better way to get subcontrol name?

I am trying to get a reference in the "scrollbar-y" of the class qx.ui.list.List
Using the createChildControl event how can I check if the widget is the one named "scrollbar-y"?
So far I found two ways of which none seems elegant but both seem to get the job done
this.__list = new qx.ui.list.List()
this.__list.addListener("createChildControl", this.__onListCreateChildControl, this);
and later
__onListCreateChildControl: function (e){
debugger;
var child = e.getData();
if (child.constructor === qx.ui.core.scroll.ScrollBar && child.getOrientation() === "vertical") {
child.addListener("scroll", this.__onListScroll, this);
}
},
This checks implicitly. Apparently if it is a scrollbar and it is vertical it is our y scrollbar. Yeah it kinda looks like a duck but I have to check for both
if (quacks like one && walks like one)
The other way is
__onListCreateChildControl: function (e){
debugger;
var child = e.getData();
if (child.$$subcontrol === 'scrollbar-y') {
child.addListener("scroll", this.__onListScroll, this);
}
},
which uses the internal variable $$subcontrol. This works fine but it uses qooxdoo internals which seems like a hack.
P.S. I did try getChildControl('scrollbar-y') in various phases but since it is created in "as needed" basis I always get null.
You're right! There is no "straightforward" possibility to retrieve the ID (or name) of a widget created as a child of another widget in terms of child control creation.
Therefore I've submitted a PR to github which does exactly that: namely retrieving the id/name of a child control by exposing the internal $$subcontrol variable via a method getSubcontrolId https://github.com/qooxdoo/qooxdoo/pull/9140
The PR is currently in review state.

need to find out the source of angularjs factory values modifier

In my angularjs application, I am using factory to store the values and share them across the controllers. But I am getting into a peculiar problem.
The following is my factory :
factory.quoteLinks = {allLinks : [], allLeftLinks :[], curSection : "-1", insType: "-1", test:"-1"};
factory.setQuoteLinks = function(qlinks, qleftLinks, qsubLink, qinsuranceType, testVal) {
factory.quoteLinks = { allLinks : qlinks, allLeftLinks : qleftLinks, curSection: qsubLink, insType: qinsuranceType, test:testVal};
};
factory.getQuoteLinks = function() {
return factory.quoteLinks;
};
As far as I Know, the values will be stored in factory.quoteLinks, only when I call factory.setQuoteLinks. So whenever I explicitly make call to factory.setQuoteLinks, the values are correctly getting stored. After a while debugging remaining part of the code, during debugging, I noticed even though I am not calling factory.setQuoteLinks, the values of allLinks in factory.quoteLinks is getting modified to some other values and I am not able to figure out from where this is getting modified even though I am not calling factory.setQuoteLinks to modify the allLinks at that particular point. Is there any possibility for me to track from where this value in the factory is getting modified, I mean the cause for this modification? I left with no clue how to figure it out?
Ax Max Sorin said you're probably modifying it outside of here because you're passing back the reference to it in factory.getQuoteLinks. If you need this changed use an angular copy:
factory.getQuoteLinks = function() {
return angular.copy(factory.quoteLinks);
};
This will return a copied quoteLinks.

Get items which are part of the same group in Firebase

I have a simple data structure of users and events. I am wanting to find all users who are attending the same events that the logged in user is. Users can attend multiple events.
My data is setup as follows
{
events:{
123:{
name: 'event1',
users:{
9876: true,
7564: true
}
}
},
users:{
9876:{
name: 'John',
events:{
123: true
}
},
7564:{
name: 'Peter',
events:{
123: true
}
}
}
}
I have the following code to achieve this, I was just wondering if I am on the right path and if my data structure is correct for this type of query (Firebaseref is an Angular factory)
FirebaseRef.child("users/" + authData.uid + "/events").orderByChild('displayName').once("value", function (snap) {
snap.forEach(function (event) {
FirebaseRef.child("events/" + event.key() + "/users").once("value", function (userSnap) {
userSnap.forEach(function (user) {
FirebaseRef.child("users/" + user.key()).once("value", function (realUserSnap) {
if (realUserSnap.key() != authData.uid) {
//This is a user who attends the same event
}
});
});
});
});
});
I would probably change that outermost query from a once('value' to an on('child_added' (and its other child_* siblings). The main advantage is that you're monitoring/synchronizing the data, instead of retrieving is just once. An added advantage is that it will remove the need for your first forEach.
Aside from that, this looks pretty common. The inner calls need to be once's, because you only want them to execute once. Most people get nervous because of the number of the number of on calls that will happen. But Firebase's data retrieval has little overhead after the initial websocket connection has been set up, so this typically performs pretty well.
There are lots of "should", "typically" and "may" in this answer, since the only way to be certain is for you to actually:
verify that the code functionally does what your application requires
measure the performance of the code in the conditions that you expect your users to encounter
If you do run into higher-than-expected latency, you could consider denormalizing the data a bit further. For example: you could keep the user's name in each event, where you now store true. With that, you would need to look up each user's name.

angular.js using highcharts-ng not maintaining series order, unless I force $apply (which throws already in progress error)

This may be unfixable, but I was hoping someone may have come across this before and found a workaround.
Highcharts-ng seems to merge series data with existing data in such a way that I cannot maintain the ordering of series. I specifically want to show two series, call them A & B, from left to right (because the form controls are organized that way).
So, I'm starting with a series A, then I add a B, then change A, and it's now in the order B & A.
I've looked at the highcharts-ng code, and I can understand why it's happening (see processSeries method below).
The only workaround that I've been able to get to work is to completely reset the series data and call $apply, which of course you cannot do in the middle of another apply.
$scope.chartConfig.series.length = 0
$scope.chartConfig.xAxis.categories.length = 0
$scope.$apply(); # force update with empty series (throws $apply already in progress error)
I'd really like to know if there's an option that I can set, or some other workaround that would allow me to use this how I'd like without having to resort to editing the directive.
var processSeries = function(chart, series) {
var ids = []
if(series) {
ensureIds(series);
//Find series to add or update
series.forEach(function (s) {
ids.push(s.id)
var chartSeries = chart.get(s.id);
if (chartSeries) {
chartSeries.update(angular.copy(s), false);
} else {
chart.addSeries(angular.copy(s), false)
}
});
}
Thanks a bunch!
Answering my own question. It seems the workaround is to have placeholders for the series in fixed positions, and do updates rather than replacements, such as:
$scope.chartConfig.series[0].data = [1,2,3]
$scope.chartConfig.series[0].name = 'foo'
$scope.chartConfig.series[1].data = [4,5,6]
$scope.chartConfig.series[1].name = 'bar'
Even doing something like:
$scope.chartConfig.series[0] = {name: 'foo', data: [1,2,3]}
$scope.chartConfig.series[1] = {name: 'bar', data: [4,5,6]}
results in the ordering issue described above.

How to Troubleshoot Angular "10 $digest() iterations reached" Error

10 $digest() iterations reached. Aborting!
There is a lot of supporting text in the sense of "Watchers fired in the last 5 iterations: ", etc., but a lot of this text is Javascript code from various functions. Are there rules of thumb for diagnosing this problem? Is it a problem that can ALWAYS be mitigated, or are there applications complex enough that this issue should be treated as just a warning?
as Ven said, you are either returning different (not identical) objects on each $digest cycle, or you are altering the data too many times.
The fastest solution to figure out which part of your app is causing this behavior is:
remove all suspicious HTML - basically remove all your html from the template, and check if there are no warnings
if there are no warnings - add small parts of the html you removed and check if the problem is back
repeat step 2 until you get a warning - you will figure out which part of your html is responsible for the problem
investigate further - the part from step 3 is responsible for either mutating the objects on the $scope or is returning non-identical objects on each $digest cycle.
if you still have $digest iteration warnings after step 1, than you are probably doing something very suspicious. Repeat the same steps for parent template/scope/controller
You also want to make sure you are not altering the input of your custom filters
Keep in mind, that in JavaScript there are specific types of objects that don't behave like you would normally expect:
new Boolean(true) === new Boolean(true) // false
new Date(0) == new Date(0) // false
new String('a') == new String('a') // false
new Number(1) == new Number(1) // false
[] == [] // false
new Array == new Array // false
({})==({}) // false
Usually that happens when you're returning a different object every time.
For example, if you use this in a ng-repeat:
$scope.getObj = function () {
return [{a: 1}, {b: 2}];
};
You're going to get this error message because Angular tries to have the "stability" and will execute the function until it returns the same result 2 times (comparing with ===), which in our case will never return true because the function always returns a new object.
console.log({} === {}); // false. Those are two different objects!
In this case, you can fix it by storing the object in scope directly, e.g.
$scope.objData = [{a: 1}, {b: 2}];
$scope.getObj = function () {
return $scope.objData;
};
That way you're always returning the same object!
console.log($scope.objData === $scope.objData); // true (a bit obvious...)
(You should never encounter that, even on complex applications).
Update: Angular has added some more in-depth explanation on their website.
Just wanted to throw this solution in here, hopefully it'll help others. I was getting this iteration problem because I was iterating over a generated property which was making a new object every time it was called.
I fixed it by caching the generated object the first time it was requested, and then always returning the cache if it existed. A dirty() method was also added, which would destroy the cached results as needed.
I had something like this:
function MyObj() {
var myObj = this;
Object.defineProperty(myObj, "computedProperty" {
get: function () {
var retObj = {};
return retObj;
}
});
}
And here's with the solution implemented:
function MyObj() {
var myObj = this,
_cached;
Object.defineProperty(myObj, "computedProperty" {
get: function () {
if ( !_cached ) {
_cached = {};
}
return _cached;
}
});
myObj.dirty = function () {
_cached = null;
}
}
There also is the possibility of it not being an infinite loop at all. 10 iterations is not a sufficiently large number to conclude that with any amount of certainty. So before going on a wild-goose chase it may be advisable to rule out that possibility first.
The easiest method to do so is increasing the maximum digest loop count to a much larger number, which can be done in the module.config method, using the $rootScopeProvider.digestTtl(limit) method. If the infdig error does no longer appear you simply have some sufficiently complex update logic.
If you build data or views relying on recursive watches you may want to search for iterative solutions (i.e. not relying on new digest loops to be started) using while, for or Array.forEach. Sometimes the structure is just highly nested and not even recursive, there probably is not much to be done in those cases except raising the limit.
Another method of debugging the error is looking at the digest data. If you pretty print the JSON you get an array of arrays. Each top level entry represents an iteration, each iteration consists of a list of watch entries.
If you for example have a property which is modified in a $watch on itself it is easy to see that the value is changing infinitely:
$scope.vm.value1 = true;
$scope.$watch("vm.value1", function(newValue)
{
$scope.vm.value1 = !newValue;
});
[
[
{
"msg":"vm.value1",
"newVal":true,
"oldVal":false
}
],
[
{
"msg":"vm.value1",
"newVal":false,
"oldVal":true
}
],
[
{
"msg":"vm.value1",
"newVal":true,
"oldVal":false
}
],
[
{
"msg":"vm.value1",
"newVal":false,
"oldVal":true
}
],
[
{
"msg":"vm.value1",
"newVal":true,
"oldVal":false
}
]
]
Of course in larger project this may not be as simple, especially since the msg field often has the value "fn: regularInterceptedExpression" if the watch is a {{ }} interpolation.
Other than that the already mentioned methods, like cutting down the HTML to find the source of the problem, are of course helpful.
I had the same problem - I was creating a new date every time. So for anyone dealing with dates I converted all calls like this:
var date = new Date(); // typeof returns object
to:
var date = new Date().getTime(); // typeof returns number
Initializing a number instead of a date object solved it for me.
the easy way is :
use angular.js,not the min file.
open it and find the line:
if ((dirty || asyncQueue.length) && !(ttl--)) {
add line below:
console.log("aaaa",watch)
and then refresh your page, in the develope tools console,you will
find you error code .
It's a known bug in ui-router, this helped us: https://github.com/angular-ui/ui-router/issues/600
I would also like to mention that I received this error message when I had a typo in the templateUrl of a custom directive that I had in my project. Due to the typo, the template could not be loaded.
/* #ngInject */
function topNav() {
var directive = {
bindToController: true,
controller: TopNavController,
controllerAs: 'vm',
restrict: 'EA',
scope: {
'navline': '=',
'sign': '='
},
templateUrl: 'app/shared/layout/top-navTHIS-IS-A-TYPO.html'
};
Look in the network tab of your web browser's dev tools, and look to see if any resource is having a 404 error.
Easy to overlook, because the error message is very cryptic and seemingly unrelated to the real issue.
I was having this issue in my project because the .otherwise() was missing my route definition and I was hitting wrong route.
I had this issue because I was doing this
var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
return ve.rawMaterial.id = rawMaterial.id;
});
Instead of this: (notice = vs ===), my unit test started breaking and I found my stupidity
var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
return ve.rawMaterial.id === rawMaterial.id;
});
I ran into this issue where I needed a dynamic tooltip... it caused angular to recalculate it every time as a new value (even though it was the same). I created a function to cache the computed value like so:
$ctrl.myObj = {
Title: 'my title',
A: 'first part of dynamic toolip',
B: 'second part of dynamic tooltip',
C: 'some other value',
getTooltip: function () {
// cache the tooltip
var obj = this;
var tooltip = '<strong>A: </strong>' + obj.A + '<br><strong>B: </strong>' + obj.B;
var $tooltip = {
raw: tooltip,
trusted: $sce.trustAsHtml(tooltip)
};
if (!obj.$tooltip) obj.$tooltip = $tooltip;
else if (obj.$tooltip.raw !== tooltip) obj.$tooltip = $tooltip;
return obj.$tooltip;
}
};
Then in the html, I accessed it like this:
<input type="text" ng-model="$ctrl.myObj.C" uib-tooltip-html="$ctrl.myObj.getTooltip().trusted">
this is how I approached it and found a solution:
I checked the text, it showed:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"statement === statment && functionCall()","newVal":[{"id":7287,"referen...
so if you can see the
msg
that's the statment generating the error. I checked the function called in this message, I returned (false) from all of them just to determine which one have the problem.
one of them was calling a function that keeps changing the return, which is the problem.
As crazy as it sounds, I fixed this error just by restarting my browser when it just cropped up all of a sudden.
So one solution is to just clear your browser's cache or try restarting the browser.

Resources