3

Scroll down for solution !!

I have a simple AngularJS application (with lodash loaded after angular.js, before app.js) :

  • Project list page(/projects), where a link on an item brings it to /projects/:id
  • In /projects/:id, the controller does a get request to an API, and returns the data
  • I assign the data to $scope.project, and display it in the view

Here's an example of the return I'm getting from the API :

{ 
    "_id" : { "$oid" : "546a3bdee4b0bfd97138fe08"} , 
    "picture" : "http://placehold.it/400x400" , 
    "name" : "Square Campus" ,  
    "address" : "293 Grafton Street, Shasta, Arizona, 6757" , 
    "phone" : "+1 (916) 544-2274" , 
    "buildings" : [ 
        { "name" : "North Campus" , "floors" : "4" , "users" : "8"} , 
        { "name" : "South Campus" , "floors" : "2" , "users" : "15"} , 
        { "name" : "East Wing" , "floors" : "8" , "users" : "23"}
    ]
}

// Using this call in the controller
$scope.project = Restangular.one('projects', $routeParams.projectId).get().$object;

And here's how I display it in the view :

        <aside class="aside-primary col-xs-12 col-md-3">
            <div class="well">
                <img ng-src="{{project.picture}}" alt="" class="img-responsive"/>
                <strong>{{project.name}}</strong><br />
                {{project.address}}<br />
                {{project.phone}}<br />
            </div>
        </aside>

        <section class="primary-content col-xs-12 col-md-9">
            <h1>{{project.name}}</h1>

            <h2>Buildings</h2>
            <table class="table table-hover table-bordered">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Floors</th>
                        <th>Users</th>
                    </tr>
                </thead>
                <tbody>
                    <tr data-ng-repeat="building in project.buildings">
                        <td>{{building.name}}</td>
                        <td>{{building.floors}}</td>
                        <td>{{building.users}}</td>
                    </tr>
                    <tr>
                        <td><strong>Total</strong></td>
                        <td></td>
                        <td></td>
                    </tr>
                </tbody>
            </table>

        </section>


Now, here's what I want to do :

I'm fairly new to Lo-Dash (and JS development is not my strongest asset) and would like to use it to calculate sums. In this case, the sum of the total number of floors and users.

Problem is, I don't know how to convert the object I'm receiving to this kind of array :

// Floors example
var sum = _.reduce([4, 2, 8], function(sum, num) {
    return sum + num;
});
console.log(sum);


Can anyone help me figure this out?

Thank you !

Solution

It is important that when you make the Restangular call, you define the "then" promise because the data isn't loaded at first on the initial pageload.

    var detailProject = Restangular.one('projects', $routeParams.projectId);
    detailProject.get().then(function (project) {
        $scope.project = project;    // Assign the owner to a model

        var sums = _.reduce(project.buildings,
          function (sums, building) {
              return {
                  floors: sums.floors + parseInt(building.floors),
                  users: sums.users + parseInt(building.users)
              };
          },
          { floors: 0, users: 0 } // initial values
        );
        console.log(sums);
    });
2
  • Why do you want to use lodash here? @Jordan' solution is good but on every iteration a new object is created. It's not so good from performance point of view. I would recommend to use simple for loop for such trivial task. Commented Nov 19, 2014 at 18:40
  • @Kiril You'd need thousands of records before you saw any real impact from object-creation overhead. Regardless, if you wanted to avoid that overhead you could instead update the properties of the sums object and return it instead of creating a new object each time (like this). Commented Nov 19, 2014 at 19:51

3 Answers 3

3

If you want to calculate the floors and users at the same time, instead of iterating over the data twice, you can do it like this:

var sums = _.reduce( $scope.project.buildings,
  function(sums, building) {
    return { floors: sums.floors + parseInt(building.floors),
             users:  sums.users + parseInt(building.users) };
  },
  { floors: 0, users: 0 } // initial values
);

console.log(sums);
// => { floors: 14,
//      users: 46 }

You could assign this to a property on your $scope.project object, in order to display it in the view:

$scope.project.buildingTotals = _.reduce( $scope.project.buildings, // ...

...then...

<tr>
    <td><strong>Total</strong></td>
    <td>{{project.buildingTotals.floors}}</td>
    <td>{{project.buildingTotals.users}}</td>
</tr>
Sign up to request clarification or add additional context in comments.

5 Comments

Is there a specific reason why you are using $project here instead of $scope.project (like I defined when I made a Restangular call). In my case, I get this error when jshint tries to validate my controller app\scripts\controllers\project-detail.js line 18 col 33 '$project' is not defined.
@eHx: Nope, just mistyped. Sorry for the confusion.
Why am I getting the initial values when I try a console log? Object {floors: 0, users: 0}. I also tried console.log(sums.floors); and I still get 0... any ideas?
Figured it out... will post solution in original post
I don't know why that's happening, but I did find an issue with the view code I wrote—instead of e.g. {{buildingTotals.floors}} it should be {{project.buildingTotals.floors}}. I've updated my answer and created a working JSFiddle here: jsfiddle.net/jrunning/gg0w4j3e
0

Something like this should work.

$project.totalFloors = _.reduce($project.buildings, function(s, entry) {
    return s + parseFloat(entry.floors);
}, 0);

$project.totalUsers = _.reduce($project.buildings, function(s, entry) {
    return s + parseFloat(entry.users);
}, 0);

Comments

0

Try this. First use pluck to get just the floors values, then use reduce on that.

Should also work for counting your users with just slight modifications. The parseInt is needed because your numbers are strings so using plus would concatenate them rather than adding them without it.

var sumFloors = _.pluck(project['buildings'], 'floors').reduce(function(previous, current){
    return parseInt(previous, 10) + parseInt(current, 10);
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.