Meteor-angular Unsure about data-binding


#1

Hi all! I’m new to Meteor and using meteor-angular to develop a very simple cryptocurrency portfolio app. At the bottom I want the app to display a value “netWorth”. netWorth is calculated by taking the “totalValue” property of each item in the mongoDB and summing them using .sum().

<p class="net-worth">
<Strong class="portfolio-total">Total Portfolio Value: ${{ netWorth }}</strong>
</p>

is the HTML and as you can see the {{ netWorth }} isn’t pushing anything to the front-end and I’m not sure what I’ve done wrong! Please help!!


#2

And your helper looks like?


#3
class PortfolioCtrl {
    constructor($scope) {
      $scope.viewModel(this);

      this.helpers({
        coins() {
          return MongoPortfolio.find({});
        }
      })
    }

    netWorth() {
      netWorth = MongoPortfolio.find({}).sum('totalValue');
      return netWorth;
    }

    addCoin(newCoin, volume, usdPrice) {
      // Calculate total USD value of this position
      totalValue = volume * usdPrice;

      // Insert coin/volume/price/total USD value into MongoDB
      MongoPortfolio.insert({
        coinName: newCoin,
        volume: volume,
        usdPrice: usdPrice,
        totalValue: totalValue
      });

      // Clear form
      this.newCoin = '';
      this.volume = '';
      this.usdPrice = '';

      // Update portfolio net worth
      netWorth = MongoPortfolio.find({}).sum('totalValue')
    }

    removeCoin(coin) {
      // Removes the coin from MongoDB and updates portfolio net worth
      MongoPortfolio.remove(coin._id);
      netWorth = MongoPortfolio.find({}).sum('totalValue')
    }

    updateCoin(coin, newName, newVolume, newPrice) {
      // Call Meteor method "coins.updateCoin" to change name, volume, and price data
      totalValue = newVolume * newPrice;
      Meteor.call('coins.updateCoin', coin._id, newName, newVolume, newPrice, totalValue);

      // Clear the input forms
      this.newName = '';
      this.newVolume = '';
      this.newPrice = '';

      // Update the net worth
      netWorth = MongoPortfolio.find({}).sum('totalValue')
    }
  }

Now that’s slightly updated code. using that it place where {{ netWorth }}'s value should appear on the front page now appears as “$function () { return _netWorth.toString(); }”


#4

Minimongo has no sum() method on its cursor. You could, however, use fetch() or map(), followed by a standard array reduce() method. Alternatively, you could do the summation inside a forEach() loop.

Even more alternatively, you could use a Meteor method which uses an aggregation pipeline.


#5

I added this package which adds the “sum()” method

https://atmospherejs.com/peppelg/mongo-sum


#6

robfallows, do you think maybe that mongo-sum package I added may be creating more issues than it solves and I use the fetch and reduce methods instead?

Also, note that I don’t technically think netWorth() is actually a helper right now. I’m still not sure how angular translate across to metor-angular and what things need to go where. Sorry if I am a bit noobish!


#7

I’ve just added that package to a test app, and it works correctly.

However, I’ve never used Angular, so don’t know what difference, if any, that might make.


#8

yeah it works, like it does properly create the value of netWorth that I need. the issue is just getting it to show up on the front-end lol


#9

You need someone with Angular experience. I’m looking at your code and thinking “is that how it’s done?”


#10

@robfallows is correct, a Mongo Cursor does not have a sum method, and if it is added from a third party package, you’d still need to know what you’re receiving (a cursor or a number).

What you’re seeing is an issue with the way you’re using Angular1’s digest cycle as well as browser’s event loop.

  • First of all, Meteor.call is async, and in your code you’re just allowing it to process without using it’s callback, which will be triggered when the method actually finishes.
  • Secondly, if netWorth is a cursor, there’d be no way to render it unless explicitely turned into a number (not cursor), which you can do using reduce() .find().fetch().reduce((sum, current) => sum += current, 0).
  • Third, netWorth is not specified as this.netWorth = ... which seems to be an undefined variable. If you’re binding $scope to this (at the begining) you need to have this.netWorth instead, otherwise it’s similar to using var netWorth = .... which is not scope.

My recommendation is:

  1. Return something at the end of coins.updateCoin method, even though it’s a true / null, this way its callback can be triggered when it finishes.
  2. Use callback in call Meteor.call('coins.updateCoin', ....., function(err, result){ /* Next logic */})
  3. Re-set this.netWorth within the callback as shown bellow.
// Use arrow funcitons to avoid overriding "this" from component
Meteor.call(........, (err, result) => {
// Clear the input forms
      this.newName = '';
      this.newVolume = '';
      this.newPrice = '';

      // Update the net worth
      this.netWorth = MongoPortfolio.find({}).fetch().reduce((sum, current) => {
           sum += current;
           // additional logic
           return sum
       }, 0);
     // log to see if changes are applying s expected.
     console.log('this.netWorth: ', this.netWorth);
});

Sometimes $scope.apply needs to be added for async functions in angular 1. Such as:

Meteor.call((..) => {

   $scope.$apply(() => {
            this.netWorth = "Timeout called!";
        });
})

Remember, console.log is your friend :slight_smile:


#11

@gabrielbalsa

Tried your suggestion, so right now my code is as follows;

class PortfolioCtrl {
    constructor($scope) {
      $scope.viewModel(this);

      this.helpers({
        coins() {
          return MongoPortfolio.find({});
        }
      })
    }

    netWorth() {
      netWorth = MongoPortfolio.find({}).sum('totalValue');
      return netWorth;
    }

    addCoin(newCoin, volume, usdPrice) {
      // Calculate total USD value of this position
      totalValue = volume * usdPrice;

      // Insert coin/volume/price/total USD value into MongoDB
      MongoPortfolio.insert({
        coinName: newCoin,
        volume: volume,
        usdPrice: usdPrice,
        totalValue: totalValue
      });

      // Clear form
      this.newCoin = '';
      this.volume = '';
      this.usdPrice = '';

      // Update portfolio net worth
      netWorth = MongoPortfolio.find({}).sum('totalValue')
    }

    removeCoin(coin) {
      // Removes the coin from MongoDB and updates portfolio net worth
      MongoPortfolio.remove(coin._id);
      netWorth = MongoPortfolio.find({}).sum('totalValue')
    }

    updateCoin(coin, newName, newVolume, newPrice) {
      // Call Meteor method "coins.updateCoin" to change name, volume, and price data
      totalValue = newVolume * newPrice;
      Meteor.call('coins.updateCoin', coin._id, newName, newVolume, newPrice, totalValue, (err, result) => {
        // Clear the input forms
      this.newName = '';
      this.newVolume = '';
      this.newPrice = '';

      // Update the net worth
      this.netWorth = MongoPortfolio.find({}).fetch().reduce((sum, current) => {
           sum += current;
           // additional logic
           return sum
       }, 0);
       
    // log to see if changes are applying s expected.
     console.log('this.netWorth: ', this.netWorth);
   });
  }
}

the console log shows this:

this.netWorth:  0[object Object][object Object][object Object][object Object][object Object]

and the actual {{ netWorth }} in the HTML is being displayed as:

“Total Portfolio Value: $function () { return _netWorth.toString(); }”


#12

Can you post what is being passed into reduce?
I think we were trying to add documents ({_id, name, etc…}) instead of the actual values you want to sum, so we need to know which coin properties you’re wanting to sum:

console.log(MongoPortfolio.find({}).fetch())

And post me the results you get along with the variable you’re asking to sum :slight_smile:


#13

Well the final output number I’m looking for is the sum of the portfolios value in USD worth.

Which means for each entry in the db there is a volume “number of coins being held” which is then multiplied by the price. The product of that, applied to each coin in the portfolio is what needs to be summed and output.

That’s why at the beginning of addCoin and updateCoin I have the “totalValue = volume*usdPrice” calculation which is then fed into the mongodb. and then the .sum() method I add was to be fed the {‘totalValue’} of each item in the db and add them all for a final result of netWorth.

the console.log(MongoPortfolio.find({}).fetch()) call yielded this output.

Array [ Object, Object, Object ]

And when I was using the .sum() method it was working perfectly to get the number I needed. the problem I was having was just getting that number to be displayed on the page properly.


#14

@gabrielbalsa this should make things easier!

just pushed it to github so you can see exactly what I’m working with. Helpers can be found in imports/components/meteorWallet

edit: but then I forgot to post the link!!!


#15

If the value was appearing, then just check that it’s set on this.netWorth, instead of only to netWorth = …


#16

Yeah @gabrielbalsa I can get the netWorth value to be generated correctly and printed in the console. But I can’t get it to write into the html properly. Could this be some sort of scope/controller issue maybe?


#17

Another issue I’m having that might be simpler to fix. When the app is running and I start typing into one of the 's to update a coin, it populates ALL of the inputs with whatever I’m typing. How can I make it so it only puts the text in the correct input fields and not all of them?


#18

So I just tried something new:

when the page loaded, before doing anything else I tried a manual console.log(netWorth), which returned “netWorth is not defined”

then I ran the updateCoin method using the UI as intended which printed the netWorth value to the console.

Then after that I did another manual console.log(netWorth) and it first returned the netWorth value, but then on the next line returned “undefined”. The attached picture shows the console log.

So it seems to definitely be some sort of scope related issue, but I’m not 100% certain how to solve it.


#19

Is this just a reactivity thing?

When the code first runs, the data’s not been synced to the client, so the sum is undefined. Later on, the data’s there and the sum works?

I don’t know how this is supposed to work with Angular, but have you tried putting your code in an autorun?


#20

@robfallows I’m not sure if it’s reactivity or what. It seems to clearly be getting two "netWorth"s, one is the number which is what I need displayed on screen the other is “undefined” which leads me to think it’s some sort of scoping issue. but the angular-meteor documentation isn’t SUPER clear for somebody like me who doesn’t have much experience with it to know exactly what’s going wrong.