Skip to content
Fix Code Error

Vuejs: Using keep-alive (Or something similar) on a slot

April 15, 2021 by Code Error
Posted By: Anonymous

I’ve got a combination of tree hierarchy and tabs in vue. So far I’ve gotten the unfolding more or less working.

I need to remove things from the dom entirely when they’re closed, because the data I’m talking about is large enough to bring a browser to its knees if it’s all just left in the dom as display:none.

Take a look at this example:

_x000D_

_x000D_

Vue.component('tabs', {_x000D_
  template: '#tabs',_x000D_
  data(){_x000D_
    return {_x000D_
      tabs: [],_x000D_
      expanded:true,_x000D_
      defaultExpanded:true,_x000D_
      activeTab: null,_x000D_
      hasChildren:false,_x000D_
    };_x000D_
  },_x000D_
  methods: {_x000D_
    toggle() {_x000D_
      this.expanded = !this.expanded;_x000D_
    },_x000D_
    activate(tab) {_x000D_
      if (this.activeTab) {_x000D_
        this.activeTab.active = false;_x000D_
      }_x000D_
      tab.active = true;_x000D_
      this.activeTab = tab;_x000D_
    },_x000D_
  },_x000D_
  mounted(){_x000D_
    for (i = 0; i < this.$slots.default.length; i++) {_x000D_
      let t = this.$slots.default[i];_x000D_
      if (t.componentOptions && t.componentOptions.tag == 'tab') {_x000D_
        this.tabs.push(t.componentInstance);_x000D_
      }_x000D_
    }_x000D_
    if (this.tabs.length) {_x000D_
      this.activeTab = this.tabs[0];_x000D_
      this.activeTab.active = true;_x000D_
    }_x000D_
    this.expanded = this.defaultExpanded;_x000D_
  },_x000D_
}); _x000D_
_x000D_
Vue.component('tab', {_x000D_
  template: '#tab',_x000D_
  data() {_x000D_
    return {_x000D_
      active: false,_x000D_
    };_x000D_
  },_x000D_
  props: ['label'],_x000D_
});_x000D_
_x000D_
app = new Vue({_x000D_
  'el': '#inst',_x000D_
}); 

_x000D_

<!-- templates -->_x000D_
<script type="text/x-template" id="tabs">_x000D_
  <div @click.stop="toggle">_x000D_
    <h1><slot name="h" /></h1>_x000D_
    <div v-show="expanded" class="children">_x000D_
        <ul><li v-for="tab in tabs" @click.stop="activate(tab)">{{tab.label}}</li></ul>_x000D_
      <div style="border:1px solid #F00"><slot /></div>_x000D_
    </div>_x000D_
</script>_x000D_
<script type="text/x-template" id="tab">_x000D_
  <strong v-show="active"><slot /></strong>_x000D_
</script>_x000D_
_x000D_
<!-- data -->_x000D_
<tabs id="inst">_x000D_
  <div slot="h">Woot</div>_x000D_
  <tab label="label">_x000D_
    <tabs>_x000D_
      <div slot="h">Weet</div>_x000D_
      <tab label="sub">Weetley</tab>_x000D_
    </tabs>_x000D_
  </tab>_x000D_
  <tab label="label2">Woot3</tab>_x000D_
</tabs>_x000D_
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script> 

_x000D_

_x000D_

_x000D_

This works fine, but if I change the v-show to v-if for performance, it loses state, tab buttons stop showing – basically lots of stuff breaks.

The problem is that as soon as I add v-if to the tab template’s slot the entire component is removed when it’s closed. This means the parent component’s tabs list is a completely different bunch of objects than the ones that show up when it’s opened a second time.

This means I can’t click on a label to open a tab, since the tabs will be different instances by the time I get to them, and all the tabs will default to closed every time I close and open the parent.

What I really need is something like <keep-alive> – where I could tell vue to keep the components alive in memory without rendering them to the dom. But when I add that the entire thing stops working. It seems like it really doesn’t work on slots, only on individual components.

So. tl;dr: How do I maintain the state of mixed trees and tabs while using v-if to keep the dom light?

Solution

Building on Bert Evans’ codepen, I created a component that is just a slot. I made a keep-alive-wrapped dynamic component that is the slot-component when active and a blank component when not. Now there is no v-if and state is preserved in the children when you close and re-open the parent.

_x000D_

_x000D_

console.clear();_x000D_
_x000D_
Vue.component('keepableSlot', {_x000D_
  template: '#keepable-slot'_x000D_
});_x000D_
_x000D_
Vue.component('tabs', {_x000D_
  template: '#tabs',_x000D_
  data() {_x000D_
    return {_x000D_
      tabs: [],_x000D_
      expanded: true,_x000D_
      activeTab: null,_x000D_
    };_x000D_
  },_x000D_
  methods: {_x000D_
    addTab(tab) {_x000D_
      this.tabs.push(tab)_x000D_
    },_x000D_
    toggle() {_x000D_
      this.expanded = !this.expanded;_x000D_
    },_x000D_
    activate(tab) {_x000D_
      if (this.activeTab) {_x000D_
        this.activeTab.active = false;_x000D_
      }_x000D_
      tab.active = true;_x000D_
      this.activeTab = tab;_x000D_
    },_x000D_
  },_x000D_
  watch: {_x000D_
    expanded(newValue) {_x000D_
      console.log(this.$el, "expanded=", newValue);_x000D_
    }_x000D_
  }_x000D_
});_x000D_
_x000D_
Vue.component('tab', {_x000D_
  props: ["label"],_x000D_
  template: '#tab',_x000D_
  data() {_x000D_
    return {_x000D_
      active: false_x000D_
    }_x000D_
  },_x000D_
  created() {_x000D_
    this.$parent.$parent.addTab(this)_x000D_
  }_x000D_
});_x000D_
_x000D_
app = new Vue({_x000D_
  'el': '#inst',_x000D_
});

_x000D_

.clickable-tab {_x000D_
  background-color: cyan;_x000D_
  border-radius: 5px;_x000D_
  margin: 2px 0;_x000D_
  padding: 5px;_x000D_
}_x000D_
_x000D_
.toggler {_x000D_
  background-color: lightgray;_x000D_
  border-radius: 5px;_x000D_
  margin: 2px 0;_x000D_
  padding: 5px;_x000D_
}

_x000D_

<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>_x000D_
<script type="text/x-template" id="tabs">_x000D_
  <div>_x000D_
    <h1 class="toggler" @click.stop="toggle">_x000D_
      <slot name="h"></slot>_x000D_
      (expanded={{expanded}})_x000D_
    </h1>_x000D_
    <keep-alive>_x000D_
      <component :is="expanded && 'keepableSlot'">_x000D_
        <div class="children">_x000D_
          <ul>_x000D_
            <li class="clickable-tab" v-for="tab in tabs" @click.stop="activate(tab)">{{tab.label}}</li>_x000D_
          </ul>_x000D_
          <div>_x000D_
            <slot></slot>_x000D_
          </div>_x000D_
        </div>_x000D_
      </component>_x000D_
    </keep-alive>_x000D_
  </div>_x000D_
</script>_x000D_
_x000D_
<script type="text/x-template" id="keepable-slot">_x000D_
  <div>_x000D_
    <slot></slot>_x000D_
  </div>_x000D_
</script>_x000D_
_x000D_
<script type="text/x-template" id="tab">_x000D_
  <strong>_x000D_
    <component :is="active && 'keepableSlot'"><slot></slot></component>_x000D_
  </div>_x000D_
</script>_x000D_
_x000D_
<!-- data -->_x000D_
<tabs id="inst">_x000D_
  <div slot="h">Woot</div>_x000D_
  <tab label="label">_x000D_
    <tabs>_x000D_
      <div slot="h">Weet</div>_x000D_
      <tab label="sub">Weetley</tab>_x000D_
    </tabs>_x000D_
  </tab>_x000D_
  <tab label="label2">Woot3</tab>_x000D_
</tabs>

_x000D_

_x000D_

_x000D_

Answered By: Anonymous

Related Articles

  • Active tab issue on page load HTML
  • vue js cant understand keep alive
  • How to prevent scrolling the whole page?
  • Stacked Tabs in Bootstrap 3
  • Vue rendering order
  • When tab is selected, show next tab.. if last tab selected,…
  • Ukkonen's suffix tree algorithm in plain English
  • Vuejs - keep-alive component toggled with v-if
  • Vuejs nested slots: how to pass slot to grandchild
  • How to make my custom tab component work with passing index…

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

Vue.js – using Vuelidate url domain should match email domain

Next Post:

Cannot pass multiple arguments in vuex actions

Leave a Reply Cancel reply

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

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error