I am trying out some angularjs stuff. So I have a few arrays. One of them is artists. this is it's basic structure from console.log(artists);
artists
problem is that I can't access the elements of the array individually. I read up a lot of things regarding associative arrays and may questions on SO but none really helped. So either it is a very silly mistake I am making or it is some thing else.
Here are few results that I got with every array I have.
console.log(artists[0]); //returns undefined
console.log(artists['0']); //returns undefined
console.log(artists.length); // returns 0 in spite of the fact it showed 20 previously
console.log(Array.isArray(artists)); //returns true
And yes I created the array like this in a service, ChartService
var artists = [];
var artistids = [];
var tracks = [];
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
});
});
});
return {
Artists : artists,
Tracks : tracks
}
And my controller
console.log(ChartService.Artists); //runs fine
console.log(ChartService.Tracks); //runs fine
$scope.tracks = ChartService.Tracks;
console.log($scope.tracks); //runs fine
console.log($scope.tracks[0]); //returns undefined
console.log($scope.tracks['0']); //returns undefined
console.log($scope.tracks.length); // returns 0 in spite of the fact it showed 20 previously
console.log(Array.isArray($scope.tracks)); //returns true
The issue is that you check the content of artists before the issued http get requests have triggered their responses.
One way to resolve that is to put your code in the success callback, like this:
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
});
});
// here
console.log(artists);
});
Still, that solves it for artists, but then you'd need to do something similar if you need the tracks: as you have more then one request providing for that, you'd need to check the length of the tracks array and only if it has the complete length, like this:
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
if (tracks.length == items.length) { // all done
console.log(artists, tracks);
}
});
});
});
In a follow-up question (in comments) you explained you need this in your controller. You might look into $watch or variants of that method. If you need assistance with that, I would suggest to ask a new question.
Related
I have the following api which returns thousands of result: http://xxx/xxx/CarRoutes. The API creator however limits only 50 results to be return at once. Hence, to see get another 50 more results to be returned, "?$skip=50" needs to be used. Also, the api url does not allow to add in any parameters behind.
Now I would like to search for CarRoutes id = 123. How can I auto increment the $skip count until results is found?
Appreciate if it can be done Javascript language.
Current idea I have, which is not efficient.
function getInfo() {
$.ajax({
url: "http://xxx/xxx/CarRoutes?$skip="+skip,
success: function(result) {
var obj = JSON.stringify(result);
var routetimeobj = JSON.parse(obj);
var data = routetimeobj['value'];
var data_filter = data.filter(element => element.CarRoute =="123");
if(data_filter.length==0){
skip+=50;
getInfo();
return;
}
});
};
If you want to follow the pattern you are using in your code, you can add a parameter to your function
function getInfo(skip)
and when you are calling again, call it like this
getInfo(skip + 50);
I'm learning FRP using Bacon.js, and would like to assemble data from a paginated API in a stream.
The module that uses the data has a consumption API like this:
// UI module, displays unicorns as they arrive
beautifulUnicorns.property.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
The module that assembles the data requests sequential pages from an API and pushes onto the stream every time it gets a new data set:
// beautifulUnicorns module
var curPage = 1
var stream = new Bacon.Bus()
var property = stream.toProperty()
var property.onValue(function(){}) # You have to add an empty subscriber, otherwise future onValues will not receive the initial value. https://github.com/baconjs/bacon.js/wiki/FAQ#why-isnt-my-property-updated
var allUnicorns = [] // !!! stateful list of all unicorns ever received. Is this idiomatic for FRP?
var getNextPage = function(){
/* get data for subsequent pages.
Skipping for clarity */
}
var gotNextPage = function (resp) {
Array.prototype.push.apply(allUnicorns, resp) // just adds the responses to the existing array reference
stream.push(allUnicorns)
curPage++
if (curPage <= pageLimit) { getNextPage() }
}
How do I subscribe to the stream in a way that provides me a full list of all unicorns ever received? Is this flatMap or similar? I don't think I need a new stream out of it, but I don't know. I'm sorry, I'm new to the FRP way of thinking. To be clear, assembling the array works, it just feels like I'm not doing the idiomatic thing.
I'm not using jQuery or another ajax library for this, so that's why I'm not using Bacon.fromPromise
You also may wonder why my consuming module wants the whole set instead of just the incremental update. If it were just appending rows that could be ok, but in my case it's an infinite scroll and it should draw data if both: 1. data is available and 2. area is on screen.
This can be done with the .scan() method. And also you will need a stream that emits items of one page, you can create it with .repeat().
Here is a draft code (sorry not tested):
var itemsPerPage = Bacon.repeat(function(index) {
var pageNumber = index + 1;
if (pageNumber < PAGE_LIMIT) {
return Bacon.fromCallback(function(callback) {
// your method that talks to the server
getDataForAPage(pageNumber, callback);
});
} else {
return false;
}
});
var allItems = itemsPerPage.scan([], function(allItems, itemsFromAPage) {
return allItems.concat(itemsFromAPage);
});
// Here you go
allItems.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
As you noticed, you also won't need .onValue(function(){}) hack, and curPage external state.
Here is a solution using flatMap and fold. When dealing with network you have to remember that the data can come back in a different order than you sent the requests - that's why the combination of fold and map.
var pages = Bacon.fromArray([1,2,3,4,5])
var requests = pages.flatMap(function(page) {
return doAjax(page)
.map(function(value) {
return {
page: page,
value: value
}
})
}).log("Data received")
var allData = requests.fold([], function(arr, data) {
return arr.concat([data])
}).map(function(arr) {
// I would normally write this as a oneliner
var sorted = _.sortBy(arr, "page")
var onlyValues = _.pluck(sorted, "value")
var inOneArray = _.flatten(onlyValues)
return inOneArray
})
allData.log("All data")
function doAjax(page) {
// This would actually be Bacon.fromPromise($.ajax...)
// Math random to simulate the fact that requests can return out
// of order
return Bacon.later(Math.random() * 3000, [
"Page"+page+"Item1",
"Page"+page+"Item2"])
}
http://jsbin.com/damevu/4/edit
I've created a couple of services that grab data from a REST API. These services return objects with names, ids, and some unique keys pertaining to a foo or a bar name. I also have a service doing the same for businesses, also with names, ids, and what foo/bar is tied to that business.
Unfortunately, the data model for this is...not ideal. Rather than just showing which foo/bar is attached to that business, it has every single foo or bar for every single business with a published: true/false key/val pair.
What I'm attempting to do is grab the URL name, loop through my foo object, check to see if the name from the current URL and the data match, and if they do store that object in $scope.results. From here, I want to loop through my businesses object and check to see if its conditionData id matches that of the new $scope.results array's id. Once this condition is met, I want to store those businesses in a $scope.businesses array. As it stands right now, I'm getting all businesses returned, rather than just the ones that have the same id as the current $scope.results id. I suspect the issue is either a) I'm a noob (most likely) or b) the published: true/false is creating issues.
Thanks in advance for any help, let me know if I need to clarify anything else. I'm still pretty new to Angular and JS as a whole, so I'm not sure if how I'm attempting to do this is super optimal. I'm open to better ideas if anyone has any.
.controller('ResultsController', function($scope, $location, getData) {
$scope.businesses = [];
$scope.results = [];
var url = $location.path().split('/')[2]; // we do this because it's always going to follow a pattern of /:base/:name
function init() {
getData.getConditions().success(function(data) {
var tempCondition = data;
var tempData;
for (var condition in tempCondition) {
tempData = tempCondition[condition];
if (url === tempData.name) {
$scope.results = tempData;
}
}
})
.error(function(data, status, headers, config) {
console.log('err: ' + data);
});
getData.getBusinesses().success(function(data) {
var tempBusinesses = data,
tempConditionData;
for (var business in tempBusinesses) {
tempConditionData = tempBusinesses[business].conditionData;
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id) {
$scope.businesses.push(tempBusinesses[business]);
}
}
}
})
.error(function(data, status, headers, config) {
console.log('err: ' + data);
});
}
init();
});
I find myself using SO as a rubber duck most of the time, I figured it out basically as soon as I finished typing the question. It was due to the published: true/false key/val pair.
All I had to do was change
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id) {
$scope.businesses.push(tempBusinesses[business]);
}
}
to
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id && tempConditionData[condition].published === true ) {
$scope.businesses.push(tempBusinesses[business]);
}
}
The two http calls you are using may also be problematic as they depend one each other. what if the first calls takes some time, your second http call returns first.
I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend
Hopefully you can help me! :)
My issue is that I have a Route that looks like this which I'm expecting to populate a list of items... i.e. "only the tagged ones, please", but it doesn't:
App.TaggedItemsListRoute = App.ItemsRoute.extend({
model: function() {
var store = this.get("store");
var storePromise = store.find("item", { has_tags: true });
var filtered = store.filter("item", function(item) {
return item.get("hasTags");
});
return storePromise.then(function(response) {
return filtered;
});
}
});
Now... that just plain doesn't work because "hasTags" returns false because it relies on "tags" which returns a ManyArray which is temporarily empty beacuse it hasn't resolved yet (see models below). This seems crappy to me. It's saying "Hey I've gone none in me!" but what I want it to be saying is "please recalculate me later" and the filter is looking for a boolean, but what I want to pass it is "hey, don't resolve the filter until all hasTags have resolved" or at least to recompute the ManyArray that it passes.
If I just pass back a promise as the return value for the filter then it sort of works...
return item.get("tags").then(function(tags){ return item.get("hasTags"); });
Except that it's actually not, beacuse filter is getting a Promise, but it's not aware of promises, apparently, so when it's looking for a boolean it gets a promise which it evaluates as true, and then it pretty much shows all the items in the list. That's not a problem until I go to a different route for items which has, say, all the items on it, then come back... and BAM it's got all the items in it... hm....
The following is how I've "gotten around" it temporarily ... ie it's still buggy, but I can live with it...
App.TaggedItemsListRoute = App.ItemsRoute.extend({
model: function() {
var store = this.get("store");
var storePromise = store.find("item", { has_tags: true });
var filtered = store.filter("item", function(item) {
var tags = item.get("tags");
if tags.get("isFulfilled") {
return item.get("hasTags");
} else {
return tags.then(function() {
return item.get("hasTags");
});
}
});
return storePromise.then(function(response) {
return filtered;
});
}
});
I think the only way to really get around this at this stage would be to use RSVP.all... any thoughts?
Actually one thing I haven't tried which I might go try now is to use setupController to do the filtering. The only trouble there would be that ALL the items would get loaded inot the list and then visually "jump back" to a filtered state after about 1 second. Painful!
Models
My Ember App (Ember 1.5.1) has two models (Ember Data beta7): Item and Tag. Item hasMany Tags.
App.Item = DS.Model.extend({
tags: DS.hasMany("tag", inverse: "item", async: true),
hasTags: function() {
return !Em.isEmpty(this.get("tags"));
}.property("tags")
});
App.Tag = DS.Model.extend(
item: DS.belongsTo("item", inverse: "tags"),
hasItem: function() {
return !Em.isEmpty(this.get("item"))
}.property("item")
);
If I change the model to the following, it actually does print something to the logs when I go to the route above, so it is fulfilling the promise.
App.Item = DS.Model.extend({
tags: DS.hasMany("tag", inverse: "item", async: true),
hasTags: function() {
this.get("tags").then(function(tags) {
console.log("The tags are loding if this is printed");
});
return !Em.isEmpty(this.get("tags"));
}.property("tags")
});
This is a spin off question from Ember Data hasMany async observed property "simple" issue because I didn't really explain my quesiton well enough and was actually asking the wrong question. I originally thought I could modify my model "hasTags" property to behave correctly in the context of my Route but I now don't think that will work properly...
This seems like a perfectly good candidate for RSVP.all. BTW if you want a rundown on RSVP I gave a talk on it a few weeks back (don't pay too much attention too it, pizza came halfway through and I got hungry, http://www.youtube.com/watch?v=8WXgm4_V85E ). Regardless, your filter obviously depends on the tag collection promises being resolved, before it should be executed. So, it would be appropriate to wait for those to resolve before executing the filter.
App.TaggedItemsListRoute = App.ItemsRoute.extend({
model: function() {
var store = this.get("store");
return store.find("item", { has_tags: true }).then(function(items){
var tagPromises = items.getEach('tags');
return Ember.RSVP.all(tagPromises).then(function(tagCollections){
// at this point all tags have been received
// build your filter, and resolve that
return store.filter("item", function(item) {
return item.get("hasTags");
});
});
});
}
});
Example using a similar idea with colors (I only show it if the relationship has 3 associated colors)
http://emberjs.jsbin.com/OxIDiVU/454/edit
On a separate note, if you felt like you wanted this hook to resolve immediately, and populate magically after, you could cheat and return an array, then populate the array once the results have come back from the server, allowing your app to seem like it's reacting super quick (by drawing something on the page, then magically filling in as the results come pouring in).
App.TaggedItemsListRoute = App.ItemsRoute.extend({
model: function() {
var store = this.get("store"),
quickResults = [];
store.find("item", { has_tags: true }).then(function(items){
var tagPromises = items.getEach('tags');
return Ember.RSVP.all(tagPromises).then(function(tagCollections){
// at this point all tags have been received
// build your filter, and resolve that
return store.filter("item", function(item) {
return item.get("hasTags");
});
});
}).then(function(filterResults){
filterResults.forEach(function(item){
quickResults.pushObject(item);
});
});
return quickResults;
}
});
Example of quick results, returns immediately (I only show it if the relationship has 3 associated colors)
http://emberjs.jsbin.com/OxIDiVU/455/edit