[SOLVED] How to delay heavy rendering in Vue.js and Vuetify

Issue

I’m using Vue.js 2 and Vuetify 2.6 and have a somewhat heavy form rendering to do in expansion panels. When clicking on the header of a panel, there is a small delay before the panel section opens, as it is first rendering I guess. The delay doesn’t bother me much, but I’d like to instantly display a progress indicator while the initial rendering is happening. Problem is, the progress indicator only appears after the rendering is complete. The click event happens instantly though, as seen in the js console of the below snippet.

Is there a way to delay the panel section’s rendering to first display the progress indicator? Surprisingly the UI isn’t frozen while the rendering happens, so the progress indicator animation works.

I can’t use the expansion panel "eager" option because I have many panels and loading everything at once takes too much time.

This code snippet illustrates the problem with an exaggerated example.

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    loading: false
  }),
  methods: {
    click() {
      console.log('click')
      this.loading = true
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css">


<v-app id="app">
  <template>
    <v-expansion-panels>
      <v-expansion-panel @click="click">
        <v-expansion-panel-header>
          <template v-slot:default="{ open }">
            <span>
              <v-progress-circular
                v-if="loading"
                indeterminate
                size="14"
                width="2"
                color="blue"
                class="d-inline-block"
              />
            </span>
            <span>Click me! opened: {{ open }}</span>
          </template>
        </v-expansion-panel-header>
        <v-expansion-panel-content>
          <v-text-field v-for="(item, i) in 1500" :key="i" />
        </v-expansion-panel-content>
      </v-expansion-panel>
    </v-expansion-panels>
  </template>
</v-app>

Solution

The problem is the effects of the loading flag change (including rendering the spinner and the panel contents) are occurring in the same macro tick.

One workaround is to split the effects into two groups: (1) toggle loading to render the spinner and to open the panel, and (2) toggle a new flag to render the panel contents. This enables moving the second effect group into the next macro tick:

  1. Add a Boolean flag as a data property, and conditionally render the panel contents with that flag.

  2. In the click handler, await a macro tick (i.e., await a setTimeout() with no timeout).

  3. Toggle the flag from step 1 to render the panel contents.

<script>
new Vue({
  data: () => ({
    1️⃣
    loadTextfields: false,
  }),
  methods: {
    async click() {
      this.loading = true
      2️⃣
      await new Promise(r => setTimeout(r))
      3️⃣
      this.loadTextfields = true
    }
  }
})
</script>
<v-expansion-panels>
  <v-expansion-panel @click="click">
    ⋮
    <v-expansion-panel-content>
                           1️⃣
      <template v-if="loadTextfields">
        <v-text-field v-for="(item, i) in 1500" :key="i"/>
      </template>
    </v-expansion-panel-content>
  </v-expansion-panel>
</v-expansion-panels>
new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    loading: false,
    loadTextfields: false,
  }),
  methods: {
    async click() {
      console.log('click')
      this.loading = true
      await new Promise(r => setTimeout(r))
      this.loadTextfields = true
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css">


<v-app id="app">
  <template>
    <v-expansion-panels>
      <v-expansion-panel @click="click">
        <v-expansion-panel-header>
          <template v-slot:default="{ open }">
            <span>
              <v-progress-circular
                v-if="loading"
                indeterminate
                size="14"
                width="2"
                color="blue"
                class="d-inline-block"
              />
            </span>
            <span>Click me! opened: {{ open }}</span>
          </template>
        </v-expansion-panel-header>
        <v-expansion-panel-content>
          <template v-if="loadTextfields">
            <v-text-field v-for="(item, i) in 1500" :key="i"/>
          </template>
        </v-expansion-panel-content>
      </v-expansion-panel>
    </v-expansion-panels>
  </template>
</v-app>

Answered By – tony19

Answer Checked By – Gilberto Lyons (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *