I'm creating a page with v-data-table. Some content of this table is loading at mounted stage, but the data for one column should be loaded line-by-line in the background by async API calls after rendering the whole table. Table rows should also be colored based on data returned from API call.
I've already developed this page, but stuck into one issue - when the table contains composite cells that was redefined by item slot (by example, a cell with icons, tooltips or spans), table row update time significantly increases.
According to business logic, the page may contain a large amount of rows, but I can't use v-data-table pagination to reduce entries count at one page.
The question is - how can I update row (in fact, just its color and one cell value) with a little performance loss as possible?
There is a Codepen with this problem. The way of loading data into the page is completely preserved in this Codepen, but API calls was replaced by promises with fixed timeout.
The problem still exists in Codepen. By default all the requests for 100 items have passed in 12-13 seconds (there's a counter at the bottom of the page). When I comment out last td, they're passed just in 7-8 seconds. When I comment out another one td (second from the end), they're passed in 6 seconds. When I increase items count to 1000, row update time is also increases.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{
text: 'Dessert (100g serving)',
value: 'name',
},
{ text: 'Second name', value: 'secondName' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Max value', value: 'maxValue' },
{ text: 'Actions', value: 'id' },
],
desserts: [],
timerStart: null,
loadingTime: null,
}
},
created() {
this.generateDesserts();
},
mounted() {
this.countMaxValues(this.desserts).then(() => {
this.loadingTime = (Date.now() - this.timerStart) / 1000;
});
},
methods: {
generateDesserts() {
let dessertNames = [
'Frozen Yogurt ',
'Ice cream sandwich ',
'Eclair',
'Cupcake',
'Gingerbread',
'Jelly bean',
'Lollipop',
'Honeycomb',
'Donut',
'KitKat',
null
];
for (let i = 0; i < 100; i++) {
let dessert = {
id: i,
name: dessertNames[Math.floor(Math.random() * dessertNames.length)],
secondName: dessertNames[8 + Math.floor(Math.random() * (dessertNames.length - 8))],
fat: Math.random() * 100,
carbs: Math.floor(Math.random() * 100),
protein: Math.random() * 10
};
this.desserts.push(dessert);
}
},
async countMaxValues(array) {
this.timerStart = Date.now();
for (const item of array) {
await this.countMaxValue(item).catch(() => {
//Even when one request throws error we should not stop others
})
}
},
async countMaxValue(item) {
await new Promise(resolve => setTimeout(resolve, 50)).then(() => {
let maxVal = Math.random() * 100;
item.maxValue = maxVal < 20 ? null : maxVal;
this.desserts.splice(item.id, 1, item);
});
}
}
})
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
:footer-props='{
itemsPerPageOptions: [-1],
prevIcon: null,
nextIcon: null,
}'
>
<template v-slot:item="props">
<tr :style="{
background: (props.item.maxValue !== null && (props.item.carbs < props.item.maxValue))
? '#ffcdd2'
: (
(props.item.maxValue !== null && (props.item.carbs > props.item.maxValue)
? '#ffee58'
: (
props.item.maxValue === null ? '#ef5350' : 'transparent'
)
)
)}">
<td>{{ props.item.name || '—' }}</td>
<td>{{ props.item.secondName || '—' }}</td>
<td>{{ props.item.fat }}</td>
<td>{{ props.item.carbs }}</td>
<td>{{ props.item.protein }}</td>
<td>
<span>
{{ props.item.maxValue || '—' }}
</span>
<v-btn v-if="props.item.name && props.item.maxValue" icon>
<v-icon small>mdi-refresh</v-icon>
</v-btn>
</td>
<td class="justify-center text-center" style="min-width: 100px">
<v-tooltip bottom v-if="props.item.name && props.item.secondName">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
class="mr-2"
small
>
format_list_numbered_rtl
</v-icon>
</template>
<span>Some action tooltip</span>
</v-tooltip>
<v-tooltip bottom v-if="props.item.name && props.item.secondName">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
class="mr-2"
small
>
edit
</v-icon>
</template>
<span>Edit action tooltip</span>
</v-tooltip>
<v-tooltip bottom v-if="props.item.name === 'KitKat'">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
small
>
delete
</v-icon>
</template>
<span>Delete action tooltip</span>
</v-tooltip>
</td>
</tr>
</template>
</v-data-table>
<p>{{ "Page loading time (sec): " + (loadingTime || '...') }}</p>
</v-app>
</div>