0

I am having a hard time using parameters for functions that I have under computed. For this example, I have an array of objects and each object is a game (board game, video game, etc). I want to show some stats about the games in my array like total games played, total hours played, how many games are video games, total price of all games, average game rating, etc.

My template for each statistic looks something like this:

<div class="game-stat">
    <p>{{ gamesPlayedCounter }}/{{ games.length }}</p>
</div>

And then in my computed I would have a function like:

gamesPlayedCounter:function(){
   var result = gameList.reduce((res, item) => item.status == 'played' ? res + 1 : res, 0);
   return result;
}

The problem is, that a lot of my stats follow this same pattern. If I wanted to count the number of video games I could do this:

gamesTypeCounter:function(){
  var result = gameList.reduce((res, item) => item.type == 'video-game' ? res + 1 : res, 0);
  return result;
}

So this gives me the total for all of the games with the type of video game. And I end up with like 10 of these for different stats. I know I should be able to just do this with one function and pass in parameters.

I thought that I could just add parameters and switch out item.type with what I want to check and then 'video-game' with the value I want to check but that doesn't work. Here is what I tried:

<div class="game-stat">
    <h4>Total Games Played:</h4>                    
    <p>{{ counter('type', 'video-game') }}/{{ games.length }}</p>
</div>

counter:function(key, value){
   var result = gameList.reduce((res, item) => item.key == value ? res + 1 : res, 0);
   return result;
}

That doesn't work so now I am not sure how to create one function that counts when given a specific key and value in Vue. Should I even be using computed for this? Any insight would be appreciated!

  • Does this answer your question? [Dynamically access object property using variable](https://stackoverflow.com/questions/4244896/dynamically-access-object-property-using-variable). Use `item[key]` instead of `item.key`, which is the same as `item["key"]`--this is unrelated to Vue. Another solution is to simply sum the values of all the keys in one loop. Or `gameList.filter(e => e[key] === value).length`. – ggorlen Jan 09 '20 at 18:42

2 Answers2

0

You can do this with a method. Here's an example:

<template>
  <ul>
    <li>Total games played: {{ countWhere('isPlayed', true) }}</li>
    <li>Total video games: {{ countWhere('type', 'video') }}</li>
  </ul>
</template>
export default {
  data() {
    return {
      gameList: [
        { name: 'doom', type: 'video', isPlayed: true },
        { name: 'chess', type: 'board', isPlayed: true },
        { name: 'checkers', type: 'board', isPlayed: false },
        { name: 'fortnite', type: 'video', isPlayed: true },
      ],
    };
  },
  methods: {
    countWhere(key, value) {
      return this.gameList.filter(game => game[key] === value).length;
    },
  },
};
David Weldon
  • 63,632
  • 11
  • 148
  • 146
0

You cannot use computed with parameters (well, technically you can by using "hack" returning new function from computed property but then it works like a normal method...just dirty and confusing). Using method as David suggest sure works but methods are really not effective because they are called every time template is re-rendered and you want to make your app as fast and effective as possible, right ?

Do this:

<template>
  <ul>
    <li>Total games played: {{ gamesStats.totalGames }}</li>
    <li>Total video games: {{ gamesStats.videoGames }}</li>
  </ul>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      gameList: [
        { name: "doom", type: "video", isPlayed: true },
        { name: "chess", type: "board", isPlayed: true },
        { name: "checkers", type: "board", isPlayed: false },
        { name: "fortnite", type: "video", isPlayed: true }
      ],
      stats: {
        totalGames: item => (item.isPlayed ? 1 : 0),
        videoGames: item => (item.type === "video" ? 1 : 0)
      }
    };
  },
  computed: {
    gamesStats() {
      const entries = Object.entries(this.stats);

      // initialise aggregator with zeroes...
      const aggregator = {};
      entries.forEach(([key, func]) => (aggregator[key] = 0));

      this.gameList.reduce(function(acc, item) {
        entries.forEach(function([key, func]) {
          acc[key] += func(item);
        });
        return acc;
      }, aggregator);

      return aggregator;
    }
  }
};
</script>

Now your gamesStats is recomputed only when something changes in gameList array (not on every re-render) and you can do more interesting things, not just compare object key to some fixed value.

And as a bonus, you have a nice clean template without unnecessary JS code..

Michal Levý
  • 33,064
  • 4
  • 68
  • 86