Henrik Clausen
Henrik Clausen

Reputation: 729

Vue: avoid repeating calculations

I have a loop, where I need to evaluate the same process in several HTML elements. It looks like this:

<template v-for="(week, wix) in weeks">
  <td v-for="(day, dix) in week.Days" :class="{delDate : moment(order.DeliveryDate).diff(moment(day.Date), 'days') == 0}">
      <v-icon v-if="dix==0 && wix==0 && moment(order.DeliveryDate).diff(moment(day.Date), 'days') < 0">fa-chevron-left</v-icon>
      <v-icon v-if="dix==6 && wix==1 && moment(order.DeliveryDate).diff(moment(day.Date), 'days') > 0">fa-chevron-right</v-icon>
  </td>
</template>

As you can see, I calculate the difference between 2 dates 3 times to evaluate the same value.

In a programming language, I'd just put the result in a variable, and use it in all 3 places, but can this be done somehow in Vue?

Is the best way to separate those inner <td> in its own component? and then calculate it once in a container template?

Upvotes: 1

Views: 277

Answers (2)

Mohsin Mehmood
Mohsin Mehmood

Reputation: 4246

You can create a method under your data methods property like this:

data: {
 methods: {
    calculateDifference(order, day) {
      return  moment(order.DeliveryDate).diff(moment(day.Date), 'days'); 
    }    
  }
}

If you want to cache result for same input parameters, you can use computed like this:

data: {
  computed: {
    calcDifference() {
      return (order, day) => {
        window.iterations++
        return moment(order.DeliveryDate).diff(moment(day.Date), 'days')
      }
    },
  },
},

And then it can be used in your template:

<template v-for="(week, wix) in weeks">
  <td v-for="(day, dix) in week.Days" :class="{delDate : calculateDifference(order, day) == 0}">
    <v-icon v-if="dix==0 && wix==0 && calculateDifference(order, day) < 0">fa-chevron-left</v-icon>
    <v-icon v-if="dix==6 && wix==1 && calculateDifference(order, day) > 0">fa-chevron-right</v-icon>
  </td>
</template>

Upvotes: 1

tony19
tony19

Reputation: 138696

I recommend computing a new array that contains the pre-calculated dates (move the calculation from the template into a computed property, e.g., named myWeeks):

export default {
  //...
  computed: {
    myWeeks() {
      return this.weeks.map((week, weekIndex) => {
        return {
          ...week,
          myDays: week.Days.map((day, dayIndex) => {
            const deliveryDueDate = deliveryDate.diff(moment(day.Date), 'days')

            return {
              ...day,
              onDelivery: deliveryDueDate === 0,
              beforeDelivery: (dayIndex === 0 && weekIndex === 0) && (deliveryDueDate < 0),
              afterDelivery: (dayIndex === 6 && weekIndex === 1) && (deliveryDueDate > 0),
            }
          })
        }
      })
    }
  }
}

Then in your template, you could use the computed myWeeks without calculation clutter:

<template v-for="week in myWeeks">
  <td v-for="day in week.myDays" :class="{delDate: day.onDelivery}">
    <v-icon v-if="day.beforeDelivery">fa-chevron-left</v-icon>
    <v-icon v-if="day.afterDelivery">fa-chevron-right</v-icon>
  </td>
</template>

Upvotes: 1

Related Questions