From the API I'm working on I need to take 2 different lists and I need to take in chunks of 20 items to avoid server timeouts.
What I built actually is this:
Items1.query().$promise
.then(function (data) {
$scope.items1 = data.list;
return Items2.query().$promise;
})
.then(function (data) {
$scope.items2 = data.list;
});
With this code I'm downloading the entire list of objects.
Both the query return:
{
list: [...],
next: true,
limit: 20,
last: 20
}
Basically it is a pagination system.
Both services are like this:
App.factory('Items1', ['$resource',
function($resource) {
return $resource('items1/:item1Id', { storeId: '#id'
}, {
query: {
method: 'GET',
isArray: false
},
update: {
method: 'PUT'
}
});
}
]);
I don't really know how to make recursive function with $resource in order to push those items in chunks of 20.
I wrote an example jsfiddle to show recursive promises. Converting it for your example, it would looks something like:
function getList(resource, list, last) {
return resource.query({last: last}).$promise.then(function(data){
list = list.concat(data.list);
if (data.next) {
return getList(resource, list, data.last);
}
return list;
});
}
getList(Items1, [], 0).$promise.then(function(list) {
$scope.items1 = list;
});
getList(Items2, [], 0).$promise.then(function(list) {
$scope.items2 = list;
});
You would need to modify your angular resources to allow you to pass the last parameter in the API call. I'm assuming that the API, if provided with that parameter, will return the next section starting from it.
Related
I am making a Web App using angularjs. I have a list of posts that are fetched from the backend in the following structure:
[
{
post: {
postID: 1,
title: "sample title",
body: "so many paragraphs here",
post_date: "2018-12-26 02:21:35"
}
tags: [
{
tagTitle: "training"
},
{
tagTitle: "another tag"
}
]
}
]
I want to filter these posts according to the tag that I have clicked on. Say I have clicked on training, I want to display posts that have the training entry in their tags's array.
Since it's an angularjs Web App, the ui-sref is posts(filter: 'training') and the path (url) will be posts?filter=training.
I have created a filter with the following code:
function postsFilter() {
return function (collection, params) {
return collection.filter(function (item) {
var i, tagsArray = item.tags, len = tagsArray.length;
for (i = 0; i < len; ++i) {
return tagsArray[i].tagTitle === (
params.filter === "none" ? tagsArray[i].tagTitle :
params.filter;)
}
});
};
}
angular.module('app').filter('postsFilter', postsFilter);
In the controller, I have the following code;
function PostsController($filter) {
angular.extend(this, {
$onInit: () => {
this.filteredPosts = $filter('postsFilter')(this.posts, this.filter);
}
}
}
In the component I have the following code:
const posts = {
bindings: {
posts: '<',
filter: '<'
},
templateUrl: 'path/to/my/posts.html',
controller: 'postsController'
};
angular.module('app').component('posts', posts).config(
[
'$stateProvider', function ($stateProvider) {
$stateProvider.state (
'posts', {
url: '/posts?filter',
component: 'posts',
params: {
filter: {
value: 'none'
}
},
resolve: {
posts: ['PostsService', function (PostsService) {
return PostsService.getPostList();
}],
filter: ['$transition$', function ($transition$) {
return $transition$.params();
}]
}
}
);
}
]);
In the posts.html I then want display the filtered posts like:
<div ng-repeat='post in $ctrl.filteredPosts'>
{{post.post.title}}
</div>
With my current code, when I click on a link to filter the posts (say ui-sref='posts({filter: 'angularjs'})), the posts don't get filtered. The url changes but the posts still display all of them. By default, url to posts shows /posts?filter=none. When I click on a link to filter the posts (say the tag is angularjs), the url changes to /posts?filter=angularjs.
How should I make this correct?
OK, now it is working after putting those missing curly braces. The problem I now have is that, if a post has two or more tags, I can't filter it with the other tags, it only works with the first tag.
In this example, it will only be filtered with training, it doesn't get filtered with another tag.. Any ideas to get this to work?
if a post has two or more tags, I can't filter it with the other tags, it only works with the first tag
You can try forming the URl like this
posts?filter=angularjs&training
And then in the filter function, form an array of filter tags. Thus you will have two arrays - Collection and Filters. Now use javascript Array Includes function for each of the filter in Filters Array. Something like below (you would need to tweak it according to your model structure):
let filteredCollection = new Array();
filters.foreach((filter) => {
if(collection.includes(filter)) {filteredCollection.push(collection)}
})
Although this includes parsing through two arrays everytime, but it is the quickest solution at hand.
I have solved the problem myself. I changed the filter code to the following (and it's working perfectly now).
function postsFilter () {
return function (collection, params) {
return collection.filter(function (item) {
var i, tagArray = item.tags, len = tagArray.length;
for (i = 0; i < len; ++i) {
if (params.filter === tagArray[i].tagTitle) {
return tagArray[i].tagTitle === (
params.filter === 'none' ? tagArray[i].tagTitle : params.filter
);
} else if (params.filter === 'none') {
return tagArray[i].tagTitle === (
params.filter === 'none' ? tagArray[i].tagTitle : params.filter
);
}
}
}
}
}
This makes what I wanted to happen but I don't like the kind of hack I've made. I've repeated the code. If someone can help make it DRY, that would be great.
When at the top of my server-side code, this works fine and the results produced are correct:
var data_playlists = {};
models.Playlist.findAll({
attributes: ['id', 'name']
}).then(function (playlists){
data_playlists['playlists'] = playlists.map(function(playlist){
return playlist.get({plain: true})
});
addsongs(data_playlists, 1);
addsongs(data_playlists, 2);
addsongs(data_playlists, 3);
});
but when it's inside one of my Express methods, it isn't functioning properly; particularly, the addsongs method is not working as it should.
function addsongs(playlist_object, id_entered){
var arraysongs = [];
models.Playlist.findOne({
attributes: ['id'],
where: {
id: id_entered
}
})
.then(function(playlist) {
playlist.getSongs().then(function (thesongs){
for(var k = 0; k < thesongs.length ; k++){
arraysongs.push(thesongs[k].Songs_Playlists.SongId);
}
playlist_object.playlists[(id_entered - 1)]['songs'] = arraysongs;
});
});
}
I cannot for the life of me figure out why it works when the top segment of code is at the top, but doesn't work when inside my app.get() call.
From your code I have conducted that you want to return playlists (id and name) together with their songs (id). First of all your code will not work because the calls of addsongs(data_playlists, id) are run before data_playlists is filled with data by code above it. Moreover, the addsongs function performs asynchronous operations returning Promises, so calling them one by one will not give expected result. I suppose you can do it completely differently.
I suggest you use include attribute of options object that can be passed to findAll() method. include says which association model you also want to return from current query. In this case you want to return playlists together with their songs (M:M relation according to your code), so you need to include Song model in the query.
function getPlaylistsWithSongs() {
return models.Playlist.findAll({
attributes: ['id', 'name'],
include: [
{
model: models.Song,
as: 'Songs', // depends on how you have declare the association between songs and playlists
attributes: ['id'],
through: { attributes: [] } // prevents returning fields from join table
}
]
}).then((playlistsWithSongs) => {
return playlistsWithSongs;
});
}
Example result of getPlaylistsWithSongs result would be (after translating it to JSON e.g. like playlistsWithSongs.toJSON())
[
{
id: 1,
name: 'playlist #1',
Songs: [
{ id: 1 },
{ id: 2 }
]
}
]
Above code returns all playlists (their id and name) with their songs (only their id). Now in your route resolver you can simply call above function to return the result
app.get('/api/playlists', function (request, response) {
response.setHeader("Content-Type", "application/json; charset=UTF-8");
getPlaylistsWithSongs().then(function(playlistsWithSongs){
response.status(200).send(JSON.stringify(playlistsWithSongs));
});
});
EDIT
In order to simply return array of IDs instead array of objects with id (songs), you need to map the result. There is no simple sequelize way to return array of IDs in such a case.
}).then((playlistWithSongs) => {
let jsonPlaylists = playlistsWithSongs.map((singlePlaylist) => {
// return JSON representation of each playlist record
return singlePlaylist.toJSON();
});
jsonPlaylists.forEach((playlist) => {
// at every playlist record we map Songs to array of primitive numbers representing it's IDs
playlist.songs = playlist.Songs.map((song) => {
return song.id;
});
// when we finish we can delete the Songs property because now we have songs instead
delete playlist.Songs;
});
console.log(jsonPlaylists);
// example output: [{ id: 1, name: 'playlist #1', songs: [1, 2, 3] }]
return jsonPlaylists;
});
I have an array that I get from a service but in the controller I get an empty value in the forEach() function. This is the code.
Controller
Here, both 'products' and 'copyProducts' are empty. I need to work with the 'copyProducts' array into the forEach() function.
app.controller("controllerApp", function($scope, serviceApp){
var products = serviceApp.query();
$scope.copyProducts = products;
angular.forEach($scope.copyProducts, function(value, key){
console.log("object: "+value);
})
});
Service
app.factory("serviceApp", function($resource){
return $resource("products.json", {}, {
getAll: {
method: "GET",
isArray: true
}
})
})
Your code is wrong since .query() is asynchronous so it doesn't finish immediately and the result is not ready on the next line synchronously. So it needs a callback function to trigger once it's done with it's work.
serviceApp.query().$promise.then(function(res) {
$scope.products = res;
$scope.copyProducts = res;
angular.forEach($scope.copyProducts, function(item) {
console.log(item)
})
});
Alternative:
serviceApp.query({}, function(res, headers){
//etc
});
By the way, if you want to use the getAll method you have defined in your resource then you would not be using query()
serviceApp.getAll().$promise.then(function(res){}).....etc
I have a question about displaying the Array length in console.log().
Here is a simple example:
vm.list = CrudService.getAllData();
function getAllFonds() {
return ResService.names.query(
succResp,
errResp
);
}
function succResp(resp) {
return resp;
}
function ResService($resource, baseUrl) {
return {
names: $resource(baseUrl + '/api/list/:Id', {
Id: '#Id'
}, {
'update': {
method: 'PUT'
}
}),
....}
}
$log.info(vm.list);
When I'm opening the console, there will be display only:
Array [ ]
Only when I click on "Array" then I see on the right side that the array contains 47 objects.
Is there a possibility to display in console:
Array [47]
?
EDIT:
When I'm using:
$log.info(vm.list.length);
it returns 0.
I think you're looking for this:
console.log(vm.list.length);
or in your case
$log.info(vm.list.length);
Sure there is
console.log(yourArray.length);
Or if you insist on that format you can do
console.log('Array['+yourArray.length+']');
Take a peek at the docs
I am trying to do a http get in angular like this:
$http
.get(some url, {
params: {
description: params.description,
from: params.from,
to: params.to
}
})
.success(function (data,status) {
$scope.info_show = data
});
here's the thing, the params object parameters are set based in user input, so if the user hasn't inputted anything for the from property (some input text fielt) it will be undefined.
my problem is that I can't just pass the params object, because it doesn't filter and if I pass undefined on even one of the properties then the web service by default will return everything, could someone show me a way of dynamically doing this?
You could use a function to filter the params object.
This one receives a list of params/properties you want to filter and the src object to filter from:
var filterParams = function(params, src) {
var result = {};
angular.forEach(params, function(p) {
if (angular.isDefined(src[p])) {
result[p] = src[p];
}
});
return result;
};
And then use it like this:
$http.get(some url, {
params: filterParams(['from', 'to', 'description'], params)
})
.success(function (data,status) {
$scope.info_show = data
});