James Muldoon
James Muldoon

Reputation: 63

How to return a list of objects from a function inside a loop

I'm trying to loop over a list of views, and for each view retrieve a list of objects associated with that view, using a service call. Each view is being assigned the result of the last call to the function, instead of the result of the function call with its parameters.

Debugging output statements in the service layer method show that it is fetching the correct values. Adding a call to the method after the loop updates the views to use the results of that call.

I'm experienced programming in Angular2 and I've never come across this issue before, but I can't see what I'm doing differently. It seems the view property is being assigned the function rather than the function result.

Searching the issue suggests it's a closure issue, but I couldn't get any of the traditional solutions for this to work.

Here's what I have:

views.forEach((view: PeriodSummaryView) => {
  view.CategorySummaries = API.getCategorySummariesByPeriod(view.Period, new Date()); // every view is given the result of the last evaluation of this function
  view.TotalSpent = this.sumAmounts('Spent', view.CategorySummaries);
  view.TotalBudgeted = this.sumAmounts('Budgeted', view.CategorySummaries);
});

and the API layer:

export default class API {

    static getCategorySummariesByPeriod(filterPeriod: Period, filterDate: Date): CategorySummary[] {
        var self = this;

        let summaries: CategorySummary[] = Categories.slice();

        summaries.forEach((category: CategorySummary) => {
            category.Spent = Expenses.filter(function (e) {
                return e.CategoryId == category.CategoryId
                    && self.isDateInPeriod(filterPeriod, filterDate, e.Date)
            }).reduce(function (acc, e) {
                return acc + e.Cost;
            }, 0);
        });

        return summaries;
    }

}

Expected: Each view should have its own list of CategorySummaries, fetched from the API method using its parameters.

Actual: All views have the same list; the result from the last call to the API method.

Note: TotalSpent and TotalBudgeted are being calculated correctly.

Upvotes: 6

Views: 126

Answers (2)

rreichel
rreichel

Reputation: 823

Looks like you may want to try using the .map method instead of .forEach. The return value of your provided function callback will be used to create the new array. Example in TypeScript:

const oddNumbers = [1, 3, 5, 7, 9]
let evenNumbers = oddNumbers.map((oddNumber: number) => {
    return oddNumber + 1;
});
// evenNumbers = [2, 4, 6, 8, 10]
// oddNumbers  = [1, 3, 5, 7, 9 ]

Upvotes: 0

Maikon Matheus
Maikon Matheus

Reputation: 789

The forEach function don't modify the original array. You must to create a new empty array and push each calculed item to the new array and return it.

export default class API {

    static getCategorySummariesByPeriod(filterPeriod: Period, filterDate: Date): CategorySummary[] {
        var self = this;

        let summaries: CategorySummary[] = Categories.slice();
        let new_arr = [];

        summaries.forEach((category: CategorySummary) => {
            category.Spent = Expenses.filter(function (e) {
                return e.CategoryId == category.CategoryId
                    && self.isDateInPeriod(filterPeriod, filterDate, e.Date)
            }).reduce(function (acc, e) {
                return acc + e.Cost;
            }, 0);
            new_arr.push(category);
        });

        return new_arr;
    }

}

Upvotes: 1

Related Questions