Reputation: 89
I'm trying to keep a component alive when moving the bound item object to a different data array. Because it gets moved, the default keep-alive tag doesn't work.
I need this to improve loading time when dynamic components in my app use external libraries.
Simplified example: (https://jsfiddle.net/eywraw8t/24419/)
HTML:
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<list-item
v-for="item in list.items"
:key="item.key"
:content="item.content">
</list-item>
</draggable>
</ul>
</div>
</div>
JS:
Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
Upvotes: 3
Views: 1752
Reputation: 23483
If the problem is just one of caching expensive html build, you can do it by removing the list-item
component from the template and building them ahead of time in app.mounted()
.
How well this works in your real-world scenario depends on the nature of item.content
and it's lifecycle.
console.clear()
const ListItem = Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
methods: {
getHtml(content) {
const li = new ListItem({propsData: {content}});
li.$mount()
return li.$el.outerHTML
}
},
mounted () {
this.lists.forEach(list => {
list.items.forEach(item => {
const cacheHtml = this.getHtml(item.content)
Vue.set( item, 'cacheHtml', cacheHtml )
})
})
},
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
ul {
margin-bottom: 20px;
}
li:hover {
color: blue;
cursor: move;
}
h1 {
font-size: 20px;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/15.0.0/vuedraggable.min.js"></script>
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<div v-for="item in list.items" :key="item.key">
<li v-html="item.cacheHtml"></li>
</div>
</draggable>
</ul>
</div>
</div>
To keep reactivity when item.content
changes, you will need a little more code.
item.content
to the cache(You may be able to do this a little more elegantly with a parameterized computed property).
To simulate an item.content change, I've added a setTimeout to mounted().
console.clear()
const ListItem = Vue.component('list-item', {
props: {
content: {
required: true
}
},
mounted () {
document.body.insertAdjacentHTML('beforeend', 'Mounted! ');
},
template: '<li>{{ content }}</li>'
})
new Vue({
el: "#app",
methods: {
getHtml(content) {
const li = new ListItem({
propsData: { content }
});
li.$mount()
return li.$el.outerHTML
},
cacheHtml(item) {
if (item.cache && item.cache.content === item.content) {
return item.cache.html
} else {
const html = this.getHtml(item.content)
const cache = {content: item.content, html}
Vue.set(item, 'cache', cache)
}
}
},
mounted () {
this.lists.forEach(list => {
list.items.forEach(item => {
this.cacheHtml(item)
})
})
setTimeout(() =>
Vue.set( this.lists[0].items[0], 'content', 'changed' )
,2000)
},
data: {
lists: [
{
title: 'List 1',
items: [
{ key: 'item1', content: 'Item 1' },
{ key: 'item2', content: 'Item 2' },
{ key: 'item3', content: 'Item 3' }
]
},
{
title: 'List 2',
items: [
{ key: 'item4', content: 'Item 4' },
{ key: 'item5', content: 'Item 5' },
{ key: 'item6', content: 'Item 6' }
]
}
]
}
})
ul {
margin-bottom: 20px;
}
li:hover {
color: blue;
cursor: move;
}
h1 {
font-size: 20px;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/15.0.0/vuedraggable.min.js"></script>
<div id="app">
<div v-for="list in lists">
<h1>{{ list.title }}</h1>
<ul>
<draggable v-model="list.items" :options="{group: 'list-items'}">
<div v-for="item in list.items" :key="item.key">
<li v-html="cacheHtml(item)"></li>
</div>
</draggable>
</ul>
</div>
</div>
Upvotes: 3
Reputation: 2644
i looked into your problem and i think i might found a solution, i cannot do it in js fiddle but i'll try and explain it:
in your js fiddle the mounted is hooked in your list-item component, so indeed every time that state changed (when dragging), the event is triggered.
i create a setup with a main templated component (componentX), with a mounted function, and then created a seperated list-item component
in my sample you will see the mounted twice at the start, that is normal since we have 2 lists! but then when you start to drag and drop you will not get additional mounted events
you can download the solution in a zip from:
http://www.bc3.eu/download/test-vue.zip
it is a vue cli project, so you can just npm run dev
to start a local server
Upvotes: 1