AngularJS $http in nested foreach - javascript

I imagine that a solution is going to involve some form of promises although I'm struggling to get my head around how I would implement promises in this scenario.
Essentially I have a service which returns $resource, I then use .query() in the controller to get the array of site contexts. The query callback function then passes the response to a $scope function ($scope.getTaskLists).
The getTaskLists function loops through the site contexts using angular.forEach(). For every loop iteration it will use $http to return any task lists within each site context. Using the .success() promise, each $http request calls another for each loop, this time looping through the $http response (task lists). For each task list, another $http request is called which gets the root folder as we need a parameter later on. The .success() promise for this $http call extracts the parameter and then calls $scope.getTaskItems, passing in various parameters including site context and list id.
The getTaskItems function then uses the above parameters to make an $http request to the list, which will return all the list items. The .success() callback here then loops through all the items and pushes the objects to the task scope.
Ultimately the structure resembles something along these lines:
- $resource.query() // get site contexts
- angular.forEach(sites)
- $http().success(... // get task lists
- angular.forEach(taskLists)
- $http().success(... // get root context
- $http().success(... // get task items
- angular.forEach(taskItems)
- $scope.tasks.push(taskItem) // push task item to $scope
What I need to do is run some code once all task items have been pushed to $scope.tasks. Is this possible using promises and is there a better way of streamlining the above code so that it isn't mangled spaghetti code consisting of ajax requests and loops?
Thanks

In my opinion your angular workflow is good. The main problem may be on the server side.
If you need ALL your nested resources. Juste expose it in your json.
In your case you have something like :
[
{"id":"1", "name":"siteContext1"},
{"id":"2", "name":"siteContext2"}
]
And probably this kind of structure for all your resources. But in your specific case your first call should meet all you needs instead of giving you only a few information about the resource.
You probably need something like this :
[
{
"id":"1",
"tasks":[{
"id":"1",
"rootContext": [{...}]
}],
"name":"siteContext1",
},
{
"id":"2",
"tasks":[{
"id":"2",
"rootContext": [{...}]
}],
"name":"siteContext2",
}
]
If you can't modify your API. Then you're actually doing it in a good way according to your API ... in my humble opinion.
Actually $http calls return promises. success() is a function fired when the promise is resolved.
Hope it helped you.

can't you change the back end called by this:
$resource.query() // get site contexts
to get you all the:
$http().success(... // get task items
you need ?
Otherwise it seems to me to be a classical flow/usecase of the promises.

Related

Pass a value from an object to another object in props

Problem
Hello to all,
Given the example code:
<Table
data1={data}
data2={columns}
/>
I've to pass data.property to columns,
I will get data.property after a successful call to a remote server, so initially data will be simply null.
I've the data inside columns displaying in this way:
const columns = [
function() {data.property ? doThis : doThat },
{object1},
{object2},
{object3},
....
]
Consideration
I cannot change the order or the dimension of the column, so I cannot merge the two objects and the const columns is in another file than the starting table.
We are using ReactTable.
Why do I need the property of Data?
I need it to do a check inside the function inside columns,
something like this:
data.property.length ? DoThis : DoThat
My Tries
I tried to merge the two object in a new one, but I messed up everything and broke my table.
By the nature of your question, it appears you require to use onFetchData() to ensure all data is present before rendering the view.
An example of this type of usage is detailed within the npm documentation usage examples for the react-table project
This is the preferred method, I am familiar with the concept writing APIs in Express, where it is required that you use the async package along with a series of functions in order to ensure all data has been generated before the callback provides the data to your view being rendered.
Hopefully this fishing rod helps you in the right direction!
If this suggestion helps you, please do edit your question and share the solution you have written to tackle the issue so future viewers of this question can also benefit.
Not sure if I understand your question correctly, but I can imagine that your code crashes due to either data or data.property being undefined, as it is still waiting for your asynchronous API call to finish.
In order to prevent this you could do the following:
( data && data.property && data.property.length ) ? DoThis : DoThat

Hack news API - fetch all news

i have a problem with https://github.com/HackerNews/API.
I need fetch all best news in Angular 7, on 30 news in first page(title, author...). How can i sent get request with next api?
This API show all Id of best story:
https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty
This api show example of one story:
https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty
I try this:
loadItem(){
this.http.get(`https://hacker-
news.firebaseio.com/v0/item/${this.id}.json?print=pretty`).subscribe(data => {
this.items.push(data as any[]);
})
}
loadBestItems(){
this.http.get('https://hacker-news.firebaseio.com/v0/beststories.json?
print=pretty').subscribe(data => {
this.bestItems.push(data as any[]);
})
}
I need 30 best news on first page
This is a bit of a loaded question, but I think we can break it down into three main questions:
1. How do you limit the number of stories returned by the hacker news api?
Since the hacker-news data is exposed through the firebase API, lets refer to the firebase docs. As indicated here, we can use the limitToFirst and orderBy options together to limit the number of results. We can simply order by the key, so your request URL would end up looking something like this:
'https://hacker-news.firebaseio.com/v0/beststories.json?
print=pretty&orderBy="$key"&limitToFirst=30'
2. How do you chain HTTP requests in Angular (make a second request that depends on the result of the first)?
This can be achieved with the mergeMap rxjs operator. This operator allows you to map the values emitted by an observable to another observable. To simplify things, imagine your initial request was to only return a single id. We could then use mergeMap to map the id to a request for the full item.
If that endpoint existed at the path beststory.json, it would look something like this.like this:
this.http.get('https://hack...v0/beststory.json').pipe(
mergeMap((id) => this.http.get(`https://hack.../v0/item/${id}`))
).subscribe((data) => {
console.log('Story: ', data);
}
Since you need to map to multiple requests, however, we will need to introduce another operator, outlined in question 3.
3. How do you make multiple HTTP requests at the same time (make a request for each item in a list)?
This can be achieved with the forkJoin rxjs operator. This operator takes an array of observables, and emits an array of their values once they are all complete. In the context of your problem, the input is an array of requests (one for each id in the initial request), and the output would be a list of items. To simplify things again, lets assume you already have an array of ids sitting around. Issuing requests for each item in the list would look something like this:
let ids = [1, 2,...];
forkJoin(ids.map((id) => this.http.get(`https://hack.../v0/item/${id}`)).subscribe((stories) => {
console.log('Stories:', stories);
});
Putting it all together
Now that we know how to map the result of a request to another observable with mergeMap, and we know how to combine the results of multiple observables into one with forkJoin, we can use them together to achieve what you're looking for:
this.http.get('https://hack....v0/beststories.json?orderBy="$key"&limitToFirst=30').pipe(
mergeMap((ids) => forkJoin(ids.map((id) => this.http.get(`https://hack...v0/item/${id}`)))),
).subscribe((stories) => {
console.log('Stories:', stories);
});
Note that in the code snippets I have excluded part of the url and unrelated query params

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

Execute Code only after Multiple Promises Fulfilled in EmberJS

I have an EmberJS ArrayController. I want to have a computed property on this controller, neurons, that is a subset of the model property. The subset is computed based on a toggle button in the sidebar which is bound to currentDataset. Another computed property, activePlots then depends on neurons; the Neuron model has a hasMany relationship to Plot, and activePlots loads all the plot objects associated with each neuron object in neurons.
Currently I'm trying to do this with mapBy, but I'm running into a problem. Each retrieval of a Neuron object's plots returns a PromiseArray. I need to manipulate all the returned plots at once. I understand I can call then on the promise result of an individual call get('plots'), but how do I execute code only after the get('plots') call has returned for ALL neurons?
neurons: ( ->
#get('model').filterBy('dataset', #get('currentDataset'))
).property('model', 'currentDataset'),
activePlots: ( ->
plots = #get('neurons').mapBy('plots')
# ...code to execute after all plots have loaded
).property('neurons')
UPDATE: Picture of console output from console.log(plotSets) inside the then callback to
Ember.RSVP.all(#get('neurons').mapBy('plots')).then (plotSets) ->
console.log(plotSets)
There is a handy method for combining promises: Ember.RSVP.all(ary) takes an array of promises and becomes a promise that is resolved when all the promises in the input array are resolved. If one is rejected, the all() promise is rejected.
This is very handy, for example, when firing off multiple parallel network requests and continuing when all of them are done.
In addition to what Steve said you can watch the nuerons.length and use Ember.scheduleOnce to schedule an update (guessed coffeescript below)
activePlots: [],
watchNuerons: ( ->
Ember.run.scheduleOnce('afterRender', this, #updatePlots);
).observes('nueron.length'),
updatePlots: ( ->
plots = #get('neurons').mapBy('plots')
# ...code to execute after all plots have loaded
#set('activePlots', plots)
)

Relating two sets of JSON data in Ember

I'm loading two sets of data separately but I'd like them to be related. Allow me to explain.
Firstly, I'm not using Ember-data but am instead using a simple $.ajax wrapper as outlined in this post from one of the Discourse team.
I have a concept of channels and programmes.
Channels JSON:
[{
"ID":94,
"Name":"BBC1"
},
{
"ID":105,
"Name":"BBC2"
}]
I have to gather the IDs from this JSON to be able to then request the programmes for those channels. So a response from the programmes endpoint will look a bit like this:
Programmes JSON:
{
"Channels": [
{
"Listings": [
{
"Id": "wcy2g",
"Genres": "Education",
"S": "2013-04-26T10:45",
"E": "2013-04-26T11:15",
"T": "Crime Scene Rescue"
}
]
},
{
"Listings": [
{
"Id": "wcwbs",
"Genres": "Current affairs,News",
"S": "2013-04-26T11:00",
"E": "2013-04-26T12:00",
"PID": "nyg",
"T": "Daily Politics"
}
]
}
]
}
Each Listings array can contain x amount of programmes and the objects in the Channels array relate to the order in which they are requested (by the IDs from the Channels.json) so in this case Channels[0] is BBC1 and Channels[1] is BBC2.
What I'd like is to request these two data sets as a single JSON request each but then somehow relate them. So having a channel controller that has x amount of programme models. I also need to render the channels and their programmes in two different templates
Was thinking I could iterate through the channels.json and use the index of the item to look up the relevant items in programmes.json and create the relationship that way.
Not too sure how to use Ember to achieve this though.
Thanks
I did something very similar to this and got it working in ember. I'll sketch out what I did, using your objects. Note that I'm fairly new to ember so a grain of salt may be necessary.
First, you'll want to have model objects for "Channels", "Channel" and "Programme". This will eventually let you have Controllers and Routers for each of those things, matching up nicely with ember's naming conventions. The ChannelsData will have many ChannelData objects in it, and each ChannelData will have many ProgrammeData objects. How do you get these populated?
In your ChannelsRoute class you can have a model() function which returns the model data for that route. Your model function can call create() on ChannelsData to create an instance, and then call a loadAll function on ChannelsData. ChannelsData implements loadAll() using your preferred flavor of ajax. The dead-simple easiest thing to do is to have that function do both of your ajax calls and build the entire tree of data.
You will then find that you'll run into trouble if your ChannelRoute class tries to call its model(), for instance if you enter a path like #/channels/105 directly into the browser. To work around that, make a simple object store of your own on your App object, something like App.ChannelsStore = {}, and when you create each Channel put a reference to it in your ChannelsStore (by id). Then your ChannelRoute.model function can look up its model from that store. But only if ChannelsRoute.model has completed first!
If the user entered that #/channels/105 route as the very first entry into your app, then your code will go through the ChannelsRoute.model() method, and immediately go through the ChannelRoute.model() method, probably before your ajax has completed. In that case you can have the ChannelRoute.model() method create a temporary Channel (with no programmes, for instance) and put that in the App.ChannelsStore. Your logic for building up the whole tree of data should then be willing to check the ChannelsStore to see if an object with a given id already exists, and if so to update it. You end up with something like:
App.ChannelRoute = Ember.Route.extend({
model: function(params) {
var channel = App.ChannelsStore[params.channel_id];
// create stub version if not found
if (!channel) {
channel = App.ChannelData.create({ID: params.channel_id});
App.ChannelsStore[params.channel_id] = channel;
}
return channel;
}
});
(You may end up building a ProgrammeStore similarly, but that's just more of the same.)
The updating of the temporary object actually demonstrates a very cool aspect of ember, which is that your ui may be presented with the values from the temporary object, but then when your ajax call completes and the Channels and Programmes are all loaded - your ui will update properly. Just make sure you update your objects with the set() method, and that your ui templates are happy to work with partial data.

Categories

Resources