Skip to content
Fix Code Error

vue js cant understand keep alive

April 2, 2021 by Code Error
Posted By: Anonymous

I’m doing tests with this code:
https://jsfiddle.net/b2qj69o1/25/

  <button
    v-for="tab in tabs"
    v-bind:key="tab.name"
    v-bind:class="['tab-button', { active: currentTab.name === tab.name }]"
    v-on:click="currentTab = tab"
  >{{ tab.name }}</button>

  <component
    v-bind:is="currentTab.component"
    class="tab"
  ></component>

And the js part

var tabs = [
  {
    name: 'Home', 
    component: function() { 
    alert();  // test
    return new Promise((resolve, reject) => resolve({ 
      template: '<div>Home component</div>' 
    }))
    }
  },
  {
    name: 'Posts',
    component: {
      template: '<div>Posts component</div>'
    }
  },
  {
    name: 'Archive',
    component: {
      template: '<div>Archive component</div>',
    }
  }
]
new Vue({
  el: '#dynamic-component-demo',
  data: {
    tabs: tabs,
    currentTab: tabs[1]
  }
})

I did the alert() test to see whether vue will re-render the component. I see that with or without wraping <component> with <keep-alive>, the alert() if called only the first time I enter the Home tab. So I have two questions:
1. What exactly keep-alive does? cause it seems that anyway the component is created only once.
2. Is it possible for vue to show/hide the tabs, instead of replacing single DOM element? But still not to load the component until it is shown.

Solution

You’ve put the alert() call in the function that asynchronously returns the component definition. This function will only get called the first time the definition of the component is accessed.

The <keep-alive> tag will cache an instance of a component so that it does not get destroyed and need to be mounted again.

Using your example, you can see that the mounted hook gets called any time you switch to the “Home” tab:

_x000D_

_x000D_

var tabs = [_x000D_
  {_x000D_
    name: 'Home', _x000D_
    component: function() { _x000D_
    return new Promise((resolve, reject) => resolve({ _x000D_
      template: '<div>Home component</div>',_x000D_
      mounted() {_x000D_
        console.log('mounted')_x000D_
      }_x000D_
    }))_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Posts',_x000D_
    component: {_x000D_
      template: '<div>Posts component</div>'_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Archive',_x000D_
    component: {_x000D_
      template: '<div>Archive component</div>',_x000D_
    }_x000D_
  }_x000D_
]_x000D_
_x000D_
new Vue({_x000D_
  el: '#dynamic-component-demo',_x000D_
  data: {_x000D_
  	tabs: tabs,_x000D_
    currentTab: tabs[1]_x000D_
  }_x000D_
})

_x000D_

.tab-button {_x000D_
  padding: 6px 10px;_x000D_
  border-top-left-radius: 3px;_x000D_
  border-top-right-radius: 3px;_x000D_
  border: 1px solid #ccc;_x000D_
  cursor: pointer;_x000D_
  background: #f0f0f0;_x000D_
  margin-bottom: -1px;_x000D_
  margin-right: -1px;_x000D_
}_x000D_
.tab-button:hover {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab-button.active {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab {_x000D_
  border: 1px solid #ccc;_x000D_
  padding: 10px;_x000D_
}

_x000D_

<script src="https://unpkg.com/vue"></script>_x000D_
_x000D_
<div id="dynamic-component-demo" class="demo">_x000D_
  <button_x000D_
    v-for="tab in tabs"_x000D_
    v-bind:key="tab.name"_x000D_
    v-bind:class="['tab-button', { active: currentTab.name === tab.name }]"_x000D_
    v-on:click="currentTab = tab"_x000D_
  >{{ tab.name }}</button>_x000D_
_x000D_
  <component_x000D_
    v-bind:is="currentTab.component"_x000D_
    class="tab"    _x000D_
  ></component>_x000D_
</div>

_x000D_

_x000D_

_x000D_

But, by wrapping the dynamic component in a <keep-alive> tag, you are telling Vue to cache a reference to the home route’s component. So the mounted hook only gets called the first time the component is instantiated:

_x000D_

_x000D_

var tabs = [_x000D_
  {_x000D_
    name: 'Home', _x000D_
    component: function() { _x000D_
    return new Promise((resolve, reject) => resolve({ _x000D_
      template: '<div>Home component</div>',_x000D_
      mounted() {_x000D_
        console.log('mounted')_x000D_
      }_x000D_
    }))_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Posts',_x000D_
    component: {_x000D_
      template: '<div>Posts component</div>'_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Archive',_x000D_
    component: {_x000D_
      template: '<div>Archive component</div>',_x000D_
    }_x000D_
  }_x000D_
]_x000D_
_x000D_
new Vue({_x000D_
  el: '#dynamic-component-demo',_x000D_
  data: {_x000D_
  	tabs: tabs,_x000D_
    currentTab: tabs[1]_x000D_
  }_x000D_
})

_x000D_

.tab-button {_x000D_
  padding: 6px 10px;_x000D_
  border-top-left-radius: 3px;_x000D_
  border-top-right-radius: 3px;_x000D_
  border: 1px solid #ccc;_x000D_
  cursor: pointer;_x000D_
  background: #f0f0f0;_x000D_
  margin-bottom: -1px;_x000D_
  margin-right: -1px;_x000D_
}_x000D_
.tab-button:hover {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab-button.active {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab {_x000D_
  border: 1px solid #ccc;_x000D_
  padding: 10px;_x000D_
}

_x000D_

<script src="https://unpkg.com/vue"></script>_x000D_
_x000D_
<div id="dynamic-component-demo" class="demo">_x000D_
  <button_x000D_
    v-for="tab in tabs"_x000D_
    v-bind:key="tab.name"_x000D_
    v-bind:class="['tab-button', { active: currentTab.name === tab.name }]"_x000D_
    v-on:click="currentTab = tab"_x000D_
  >{{ tab.name }}</button>_x000D_
_x000D_
  <keep-alive>_x000D_
    <component_x000D_
      v-bind:is="currentTab.component"_x000D_
      class="tab"    _x000D_
    ></component>_x000D_
  </keep-alive>_x000D_
</div>

_x000D_

_x000D_

_x000D_

Here’s the documentation on using the <keep-alive> tag with dynamic components.


As for your second question: if you want to add all of the tabs to the DOM initially and simply hide the inactive tab components, then render each tab using the v-for directive, and use the v-show directive to only show the active tab.

Here’s an example:

_x000D_

_x000D_

var tabs = [_x000D_
  {_x000D_
    name: 'Home', _x000D_
    component: function() { _x000D_
    return new Promise((resolve, reject) => resolve({ _x000D_
      template: '<div>Home component</div>',_x000D_
    }))_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Posts',_x000D_
    component: {_x000D_
      template: '<div>Posts component</div>'_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Archive',_x000D_
    component: {_x000D_
      template: '<div>Archive component</div>',_x000D_
    }_x000D_
  }_x000D_
]_x000D_
_x000D_
new Vue({_x000D_
  el: '#dynamic-component-demo',_x000D_
  data: {_x000D_
  	tabs: tabs,_x000D_
    currentTab: tabs[1]_x000D_
  }_x000D_
})

_x000D_

.tab-button {_x000D_
  padding: 6px 10px;_x000D_
  border-top-left-radius: 3px;_x000D_
  border-top-right-radius: 3px;_x000D_
  border: 1px solid #ccc;_x000D_
  cursor: pointer;_x000D_
  background: #f0f0f0;_x000D_
  margin-bottom: -1px;_x000D_
  margin-right: -1px;_x000D_
}_x000D_
.tab-button:hover {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab-button.active {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab {_x000D_
  border: 1px solid #ccc;_x000D_
  padding: 10px;_x000D_
}

_x000D_

<script src="https://unpkg.com/vue"></script>_x000D_
_x000D_
<div id="dynamic-component-demo" class="demo">_x000D_
  <button_x000D_
    v-for="tab in tabs"_x000D_
    v-bind:key="tab.name"_x000D_
    v-bind:class="['tab-button', { active: currentTab.name === tab.name }]"_x000D_
    v-on:click="currentTab = tab"_x000D_
  >{{ tab.name }}</button>_x000D_
_x000D_
  <component_x000D_
    v-for="tab in tabs"_x000D_
    v-bind:key="'component-' + tab.name"_x000D_
    v-bind:is="tab.component"_x000D_
    class="tab"_x000D_
    v-show="currentTab === tab"_x000D_
  ></component>_x000D_
</div>

_x000D_

_x000D_

_x000D_

And, if I understand your last sentence correctly, if you really want to not create a tab component until the tab is initially active, but then want to hide the tab’s HTML content when another tab is active, you’d need to keep track of which tabs have been activated in a data property and then use the v-if directive to initially prevent the component from initializing.

Something like this:

_x000D_

_x000D_

var tabs = [_x000D_
  {_x000D_
    name: 'Home', _x000D_
    component: function() { _x000D_
    return new Promise((resolve, reject) => resolve({ _x000D_
      template: '<div>Home component</div>',_x000D_
    }))_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Posts',_x000D_
    component: {_x000D_
      template: '<div>Posts component</div>'_x000D_
    }_x000D_
  },_x000D_
  {_x000D_
    name: 'Archive',_x000D_
    component: {_x000D_
      template: '<div>Archive component</div>',_x000D_
    }_x000D_
  }_x000D_
]_x000D_
_x000D_
new Vue({_x000D_
  el: '#dynamic-component-demo',_x000D_
  data: {_x000D_
  	tabs: tabs,_x000D_
    currentTab: tabs[1],_x000D_
    activatedTabs: {}_x000D_
  },_x000D_
  watch: {_x000D_
    currentTab: {_x000D_
      immediate: true,_x000D_
      handler(tab) {_x000D_
        this.$set(this.activatedTabs, tab.name, true);_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
})

_x000D_

.tab-button {_x000D_
  padding: 6px 10px;_x000D_
  border-top-left-radius: 3px;_x000D_
  border-top-right-radius: 3px;_x000D_
  border: 1px solid #ccc;_x000D_
  cursor: pointer;_x000D_
  background: #f0f0f0;_x000D_
  margin-bottom: -1px;_x000D_
  margin-right: -1px;_x000D_
}_x000D_
.tab-button:hover {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab-button.active {_x000D_
  background: #e0e0e0;_x000D_
}_x000D_
.tab {_x000D_
  border: 1px solid #ccc;_x000D_
  padding: 10px;_x000D_
}

_x000D_

<script src="https://unpkg.com/vue"></script>_x000D_
_x000D_
<div id="dynamic-component-demo" class="demo">_x000D_
  <button_x000D_
    v-for="tab in tabs"_x000D_
    v-bind:key="tab.name"_x000D_
    v-bind:class="['tab-button', { active: currentTab.name === tab.name }]"_x000D_
    v-on:click="currentTab = tab"_x000D_
  >{{ tab.name }}</button>_x000D_
_x000D_
  <component_x000D_
    v-for="tab in tabs"_x000D_
    v-if="activatedTabs[tab.name]"_x000D_
    v-bind:key="'component-' + tab.name"_x000D_
    v-bind:is="tab.component"_x000D_
    class="tab"_x000D_
    v-show="currentTab === tab"_x000D_
  ></component>_x000D_
</div>

_x000D_

_x000D_

_x000D_

Answered By: Anonymous

Related Articles

  • Ubuntu apt-get unable to fetch packages
  • How to add fade animation for this tab
  • Data not catching in multi step contact form
  • Need help understanding this promise and handling the error
  • Is it possible to apply CSS to half of a character?
  • Vuejs - keep-alive component toggled with v-if
  • Active tab issue on page load HTML
  • Angular: Can't find Promise, Map, Set and Iterator
  • Promise chaining unhandled promise
  • How do I return the response from an asynchronous call?

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:

Backbone.Model => Uncaught TypeError: Object [Object object] has not method ‘apply’

Next Post:

VueJS how to listen to event from method not from template

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