How do I call another function within ng-repeat in Angular - javascript

I am sure this question has been already asked before, but I could not see or find a best explanation of it, therefore I would like to re-ask the same question and open the thread for more clear answers with some examples.
My goal is to display teams and its ranking, note: for displaying ranking I have a separate function that gets the ranking of team depending on their score field in db.
I have two functions within a TeamController as below:
tm.showAllByClass = function (classId) {
TeamService.showAllByClass(classId).then(function (response) {
tm.teamsInClass = response.data;
}).catch(function (error) {
$scope.result = error;
});
};
tm.ranking = function (classId, teamId) {
TeamService.ranking(classId, teamId).then(function (response) {
return response.data;
}).catch(function (error) {
$scope.result = error;
});
};
<tr ng-repeat="tm in team.teamsInClass.data">
<td>{{tm.group_number}}</td>
<td>{{tm.role.name}}</td>
<td>{{tm.ranking(tm.class_id, tm.id)}}</td>
<td>{{tm.amount | currency}}</td>
</tr>
And this is the function in backend part that gets all teams:
public function findAllTeamsInClass($classId)
{
return Team::where('class_id', '=', $classId)->with('role', 'business')->get();
}
// return rank of team
public function teamRanking($classId, $teamId){
return 3; // for sake of simplicity I just return a static value
}
Is there any way I can attach teamRanking function directly to the team entitiy as relationship or something?
For some reason tm.ranking() is not returning anything, how can I call a function that returns a value within the ng-repeat.

Since the fetch of individual rankings is asynchronous, those operations need to be chained from the fetch of the list of teams.
team.showAllByClass = function (classId) {
TeamService.showAllByClass(classId).then(function (response) {
team.teamsInClass = response.data;
//return for chaining
return team.teamsInClass;
}).then(function(teamsInClass) {
promiseArray = [];
for (var iTeam=0; iTeam<teamsInClass.length; iTeam++) {
//IIFE
(function (iTeam) {
var iPromise = TeamService.ranking(classId, iTeam.id);
iPromise = iPromise.then(function(response) {
var ranking = response.data;
team.teamsInClass[iTeam].ranking = ranking;
return ranking;
});
promiseArray.push(iPromise);
})(iTeam);
//end IIFE
};
//return for chaining
return $q.all(promiseArray);
}).then(function(rankingArray) {
console.log("All rankings fetched");
}).catch(function (error) {
$scope.result = error;
});
};
The above example fetches the list of teams and then creates a list of promises that attach each ranking to each team. It uses $q.all to chain the list of promises.
The HTML
<tr ng-repeat="tm in team.teamsInClass">
<td>{{tm.group_number}}</td>
<td>{{tm.role.name}}</td>
<td>{{tm.ranking || 'PENDING' }}</td>
<td>{{tm.amount | currency}}</td>
</tr>
The ranking will display as PENDING until the data is retrieved from the API.

You might have the same answer here:
https://stackoverflow.com/a/26400951/6715875
you can simply call a function inside ng-repeat same as normal one.

Related

Call function after two promises resolve in angularJS, one using result from other

In a controller function, I make some operations:
Get a list of organizations with a promise
In the then of this promise, I loop through each of them to extract some data and populate some of my controller attributes.
One of this operation is to call another promise to gather all users attached to this organization, with a loop inside of it to extract name and other stuff.
When I get ALL of it, so every organization has been parsed, and within them all users too, I must call a function to update my view.
I got it working by setting some flags (orgParsed and usersParsed) but I find it to be... a code shame.
I heard about a way of maybe doing this by using $q to wait for the two promises and maybe loops inside their "then" to be resolve before calling my view function. But I struggle applying this code change since the second promise use the result of the first to gather the organization ID.
Here is my current code:
this.getOrgData = function () {
return Service.getList().then(function (result) {
var orgCount = result.Objects.length;
var orgParsed = 0;
_.forEach(result.Objects, function (org) {
org.Users = [];
// Some logic here using 'org' data
Service.getUsers(org.Id, 0, 0).then(function (userResult) {
usersParsed = 0;
_.forEach(userResult.Objects, function (user) {
// Some Logic here using 'user.Name'
usersParsed++;
});
orgParsed++;
if (orgParsed === orgCount && usersParsed === userResult.Objects.length) {
self.sortMenuList(); // My view Function
}
});
});
$scope.$broadcast("getOrgData");
});
};
Do you see any way to trigger my self.sortMenuList() function only when I can be sure I got all users of every companies parsed in more elegant/efficient/safe way?
Yes, that counting should definitely be replaced by $q.all, especially as you did not bother to handle any errors.
this.getOrgData = function () {
return Service.getList().then(function (result) {
$scope.$broadcast("getOrgData"); // not sure whether you want that here before the results from the loop
return $q.all(_.map(result.Objects, function (org) {
org.Users = [];
// Some logic here using 'org' data
return Service.getUsers(org.Id, 0, 0).then(function (userResult) {
_.forEach(userResult.Objects, function (user) {
// Some Logic here using 'user.Name'
});
});
}));
}).then(function() {
self.sortMenuList(); // My view Function;
})
};
The problem you describe sounds like you want to wait until a certain amount of promises are all resolved, and then do something with the result. That's really easy when you use Promise.all():
this.getOrgData = function () {
return Service.getList().then(function (result) {
var promises = [];
_.forEach(result.Objects, function (org) {
org.Users = [];
// Some logic here using 'org' data
// Store the promise for this user in the promises array
promises.push(Service.getUsers(org.Id, 0, 0));
});
// userResults is an array of all the results of the promises, in the same order as the getUsers was called
Promise.all(promises).then(function (userResults) {
_.forEach(userResults, function(userResult) {
_.forEach(userResult.Objects, function (user) {
// Some Logic here using 'user.Name'
});
});
self.sortMenuList();
});
$scope.$broadcast("getOrgData");
});
};

Javascript promises chained inside Loop

I'm just learning Javascript and still pretty new to Parse's cloud code. I've been reading some articles and questions on promises and closures and still don't quite understand how to accomplish what I want do do yet. All the other questions/answer seem to be slightly different or difficult to understand.
I have a function that starts with a query that gets all the "Gyms". For each of those gyms, I need to run several other queries. All those inner queries (inside the loop) need to all complete before I can generate a final report for the gym. I want to understand the following things:
a.) How to allow the correct gym object from that each iteration of the loop to be accessible through the entire chain of queries in that iteration.
b.) Will all the results from the previously executed queries in my chain be available in the following queries? e.g. Can I access newWorkouts in the last function?
function createReports() {
var gymQuery = new Parse.Query(Parse.Object.extend("Gym"));
gymQuery.find({
success: function(results) {
for (var i = 0; i < results.length; ++i) {
/* jshint loopfunc: true */
var gym = results[i];
var newWorkoutsQuery = new Parse.Query(Parse.Object.extend("Workout"));
newWorkoutsQuery.equals("gym", gym);
newWorkoutsQuery.find().then(function(newWorkouts) {
var newLogsQuery = new Parse.Query(Parse.Object.extend("WorkoutLog"));
newLogsQuery.equals("gym", gym);
return newLogsQuery.find();
}).then(function(logsLastWeek) {
//Generate final report for gym using data from all above queries.
//Need access to gym, newWorkouts, and logsLastWeek
});
}
},
error:function() {
console.log("error");
}
});
}
Promise.all() should be able to help you out with this.
First, let's break out a function that retrieves the data for a single gym:
function getGymData(gym) {
var newWorkoutsQuery = new Parse.Query(Parse.Object.extend("Workout"));
newWorkoutsQuery.equals("gym", gym);
var newLogsQuery = new Parse.Query(Parse.Object.extend("WorkoutLog"));
newLogsQuery.equals("gym", gym);
return Promise.all([newWorkoutsQuery.find(), newLogsQuery.find()])
.then(function (results) {
return {
gym: gym,
workouts: results[0],
logs: results[1]
};
});
}
Then use Promise.all() across all the gyms:
function createReports() {
var gymQuery = new Parse.Query(Parse.Object.extend("Gym"));
return gymQuery.find()
.then(function (gyms) {
return Promise.all(gyms.map(getGymData));
})
.then(function (results) {
// results should be an array of objects, each with
// the properties gym, workouts, and logs
})
.catch(function (error) {
console.error(error);
});
}

AngularJs $filter in service using function

I got stuck with learning angular in particular the $filter('filter') function and how to pass a local function to it as a filter function.
The API (json files) contain all the trips in trips.json and the id array of all the trips the user has been on in the user.trips.json.
The factory getting fetching the data looks like this:
app.factory("tripsApi", function ($http) {
return {
allTrips: $http.get('/api/trips.json')
.success(function (response) {
return response;
})
.error(function (error) {
return error;
}),
userTrips: $http.get('api/user.trips.json')
.success(function (response) {
return response;
})
.error(function (error) {
return error;
})
}
});
The next piece of code is just a service to retrieve all the user trips information. The service uses a factory to access the API (in this case just the men json files). And it SHOULD filter through the trips information to retrieve only the ones the user has been on using the id array.
app.service("trips", function (tripsApi, $filter) {
var self = this;
this.user = {
trips: Array()
};
this.trips = Array();
this.getUserTrips = function () {
self.getAllTrips.then(function () {
tripsApi.userTrips.success(function (response) {
self.user.trips = $filter('filter')
(self.trips, self.containsChild(id, response));
});
});
};
this.getAllTrips = tripsApi.allTrips.success(function (response) {
self.trips = response;
});
this.containsChild = function (id, idsArray) {
if (id != 0 && idsArray != null) {
for (var i = 0; i < idsArray.length(); i++) {
if (idsArray[i] == i)
return true;
return false;
}
}
}
});
Yet I can't get it to work. The first error I get is the id not defined in the self.containsChild(id, response);
Where are the mistakes? Any help is welcome :)
The id issue, the call should be:
$filter('filter')(self.trips, self.containsChild); // this won’t really work, though, because your `containsChild` function is not properly defined.
Second, This is not a properly defined service. You want something that looks like the following
app.service('ServiceName', [ '$filter', function ($filter) {
var myInstance = {};
myInstance.containsChild = function (value, index, array) {
};
myInstance.user = . . .;
return myInstance;
}]);
Third, fix your containsChild function to take three parameters. The first will be the value passed to it, the second will be the index, and the third will be the array being filtered.
Yet I can't get it to work. The first error I get is the id not
defined in the self.containsChild(id, response);
Where are the mistakes?
You passed id to the function but there's no idvariable declared anywhere in the code the you provided which is what's causing the error.
Also, I've observed that there's no consistency in your code, you assigned this to self but you keep using this and self everywhere in your code.

Recursive queries with promises in AngularJS

I have a recursive query that needs to potentially make further queries based on the results. I would ideally like to be able to construct a promise chain so that I know when all of the queries are finally complete.
I've been using the example from this question, and I have the following method:
this.pLoadEdges = function(id,deferred) {
if (!deferred) {
deferred = $q.defer();
}
$http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
for (var i = 0; i < data.length; i++) {
var subID = data[i].EndNode;
edgeArray.push(data[i]);
self.pLoadEdges(subID, deferred);
}
} else {
deferred.resolve();
return deferred.promise;
}
});
deferred.notify();
return deferred.promise;
}
Which I then start elsewhere using:
self.pLoadEdges(nodeID).then(function() {
var edgedata = edgeArray;
});
And of course I intend to do some more stuff with the edgeArray.
The problem is that the then() function is trigged whenever any individual path reaches an end, rather than when all the paths are done. One particular pathway might be quite shallow, another might be quite deep, I need to know when all of the pathways have been explored and they're all done.
How do I construct a promise array based on this recursive query, ideally so that I can use $q.all[] to know when they're all done, when the number of promises in the promise array depends on the results of the query?
I'm not 100% positive what the end result of the function should be, but it looks like it should be a flat array of edges based on the example that you provides. If that's correct, then the following should work
this.pLoadEdges = function(id) {
var edges = [];
// Return the result of an IIFE so that we can re-use the function
// in the function body for recursion
return (function load(id) {
return $http.post('/Create/GetOutboundEdges', { id: id }).then(function(response) {
var data = response.data;
if (data.length > 0) {
// Use `$q.all` here in order to wait for all of the child
// nodes to have been traversed. The mapping function will return
// a promise for each child node.
return $q.all(data.map(function(node) {
edges.push(node);
// Recurse
return load(node.EndNode);
});
}
});
}(id)).then(function() {
// Change the return value of the promise to be the aggregated collection
// of edges that were generated
return edges;
});
};
Usage:
svc.pLoadEdges(someId).then(function(edgeArray) {
// Use edgeArray here
});
You need $q.all function:
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Update 1
Check this demo: JSFiddle
The controller can be like following code (well, you may want to put it in a factory).
It loads a list of users first, then for each user, load the posts of this user. I use JSONPlaceholder to get the fake data.
$q.all accepts an array of promises and combine them into one promise. The message All data is loaded is only displayed after all data is loaded. Please check the console.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
function load() {
return $http.get('http://jsonplaceholder.typicode.com/users')
.then(function (data) {
console.log(data.data);
var users = data.data;
var userPromises = users.map(function (user) {
return loadComment(user.id);
});
return $q.all(userPromises);
});
}
function loadComment(userId) {
var deferred = $q.defer();
$http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId).then(function (data) {
console.log(data);
deferred.resolve(data);
});
return deferred.promise;
}
load().then(function () {
console.log('All data is loaded');
});
}]);
Update 2
You need a recursive function, so, check: JSFiddle.
The code is below. I use round to jump out of the recursion because of the fake API. The key is here: $q.all(userPromises).then(function () { deferred.resolve(); });. That tells: Please resolve this defer object after all promises are resolved.
angular.module('Joy', [])
.controller('JoyCtrl', ['$scope', '$q', '$http', function ($scope, $q, $http) {
var round = 0;
function load(userId) {
return $http.get('http://jsonplaceholder.typicode.com/posts?userId=' + userId)
.then(function (data) {
var deferred = $q.defer();
console.log(data.data);
var posts = data.data;
if (round++ > 0 || !posts || posts.length === 0) {
deferred.resolve();
} else {
var userPromises = posts.map(function (post) {
return load(post.userId);
});
$q.all(userPromises).then(function () {
deferred.resolve();
});
}
return deferred.promise;
});
}
load(1).then(function () {
console.log('All data is loaded');
});
}]);
You can try building up an array of returned promises and then use the $.when.apply($, <array>) pattern. I've used it before to accomplish a similar thing to what you're describing.
More info on this SO thread.
UPDATE:
You probably also want to read the docs on the apply function, it's pretty neat.

Multiple Q.all inside function?

I want to send a list of new books to a user. So far the below code works fine. The problem is that I don't want to send a book multiple times, so I want to filter them.
Current code works fine:
function checkActiveBooks(books) {
var queue = _(books).map(function(book) {
var deferred = Q.defer();
// Get all alerts on given keywords
request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
if (error) {
deferred.reject(error);
}
var books = JSON.parse(body);
if (!_.isEmpty(books)) {
// Loop through users of current book.
var userBooks = _(book.users).map(function(user) {
// Save object for this user with name and deals.
return {
user: user,
book: book.name,
books: books
}
});
if (_.isEmpty(userBooks)) {
deferred.resolve(null);
} else {
deferred.resolve(userBooks);
}
} else {
deferred.resolve(null);
}
});
return deferred.promise;
});
return Q.all(queue);
}
But now I want to filter already sent books:
function checkActiveBooks(books) {
var queue = _(books).map(function(book) {
var deferred = Q.defer();
// Get all alerts on given keywords
request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
if (error) {
deferred.reject(error);
}
var books = JSON.parse(body);
if (!_.isEmpty(books)) {
// Loop through users of current book.
var userBooks = _(book.users).map(function(user) {
var defer = Q.defer();
var userBook = user.userBook.dataValues;
// Check per given UserBook which books are already sent to the user by mail
checkSentBooks(userBook).then(function(sentBooks) {
// Filter books which are already sent.
var leftBooks = _.reject(books, function(obj) {
return sentBooks.indexOf(obj.id) > -1;
});
// Save object for this user with name and deals.
var result = {
user: user,
book: book.name,
books: leftBooks
}
return deferred.resolve(result);
});
return Q.all(userBooks);
} else {
deferred.resolve(null);
}
});
return deferred.promise;
});
return Q.all(queue);
}
But above code doesn't work. It doesn't stop looping. I thought it made sense to use q.all twice, because it contains two loops. But I guess I'm doing it wrong...
First of all you should always promisify at the lowest level. You're complicating things here and have multiple deferreds. Generally you should only have deferreds when converting an API to promises. Promises chain and compose so let's do that :)
var request = Q.nfbind(require("request")); // a promised version.
This can make your code in the top section become:
function checkActiveBooks(books) {
return Q.all(books.map(function(book){
return request('http://.../books?l=0&q=' + book.name)
.get(1) // body
.then(JSON.parse) // parse body as json
.then(function(book){
if(_.isEmpty(book.users)) return null;
return book.users.map(function(user){
return {user: user, book: book.name, books: books };
});
});
});
}
Which is a lot more elegant in my opinion.
Now, if we want to filter them by a predicate we can do:
function checkActiveBooksThatWereNotSent(books) {
return checkActiveBooks(books).then(function(books){
return books.filter(function(book){
return checkSentBooks(book.book);
});
});
}
It's worth mentioning that the Bluebird library has utility methods for all this like Promise#filter and Promise#map that'd make this code shorter.
Note that if checkSentBook is asynchronous you'd need to modify the code slightly:
function checkActiveBooksThatWereNotSent(books) {
return checkActiveBooks(books).then(function(books){
return Q.all(books.map(function(book){ // note the Q.all
return Q.all([book, checkSentBooks(book.book)]);
})).then(function(results){
return results.filter(function(x){ return x[1]; })
.map(function(x){ return x[0]; });
});
});
}
Like I said, with different libraries this would look a lot nicer. Here is how the code would look like in Bluebird which is also two orders of magnitude faster and has good stack traces and detection of unhandled rejections. For fun and glory I threw in ES6 arrows and shorthand properties:
var request = Promise.promisify(require("request"));
var checkActiveBooks = (books) =>
Promise.
map(books, book => request("...&q=" + book.name).get(1)).
map(JSON.parse).
map(book => book.users.length ?
book.users.map(user => {user, books, book: book.name) : null))
var checkActiveBooksThatWereNotSent = (books) =>
checkActiveBooks(books).filter(checkBookSent)
Which I find a lot nicer.
Acting on #Benjamins's suggestion, here is what the code would look like when checkSentBooks returns a promise:
var request = Q.nfbind(require("request")); // a promised version.
function checkActiveBooks(books) {
return Q.all(_(books).map(function(book) {
// a callback with multiple arguments will resolve the promise with
// an array, so we use `spread` here
return request('http://localhost:5000/books?l=0&q=' + book.name).spread(function(response, body) {
var books = JSON.parse(body);
if (_.isEmpty(books)) return null;
return Q.all(_(book.users).map(function(user) {
return checkSentBooks(user.userBook.dataValues).then(function(sentBooks) {
// ^^^^^^ return a promise to the array for `Q.all`
return {
user: user,
book: book.name,
books: _.reject(books, function(obj) {
return sentBooks.indexOf(obj.id) > -1;
})
};
});
}));
});
}));
}

Categories

Resources