Vuejs: Using keep-alive (Or something similar) on a slot
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:
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.
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
Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.