Skip to main content

Vue.js Dynamic Components

Introduction

Dynamic components are a powerful feature in Vue.js that allow you to switch between different components dynamically at runtime. This gives you the flexibility to create interfaces where components can be swapped in and out based on user interactions, data changes, or application state.

In this tutorial, we'll explore Vue's dynamic components, understand how they work, and learn to implement them in various real-world scenarios.

What are Dynamic Components?

In Vue.js, dynamic components are implemented using the special <component> element with the :is attribute. This allows you to:

  • Switch between multiple components in the same mounting point
  • Change components based on user interaction
  • Create tabbed interfaces, multi-step forms, or any UI that requires component swapping

Basic Usage of Dynamic Components

Let's start with a simple example of using dynamic components:

html
<template>
<div>
<button @click="currentComponent = 'component-a'">Show A</button>
<button @click="currentComponent = 'component-b'">Show B</button>
<button @click="currentComponent = 'component-c'">Show C</button>

<component :is="currentComponent"></component>
</div>
</template>

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
import ComponentC from './ComponentC.vue';

export default {
components: {
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC
},
data() {
return {
currentComponent: 'component-a'
}
}
}
</script>

In this example:

  1. We import three components
  2. We register them in the components option
  3. We use data to track which component should be currently displayed
  4. We use the <component :is="currentComponent"> element to render the currently selected component
  5. When the user clicks a button, we change which component is displayed

Keep-Alive with Dynamic Components

One challenge with dynamic components is that they are re-created each time you switch between them. This means they lose their state when they're switched out. To preserve the state of components, Vue provides the <keep-alive> wrapper:

html
<template>
<div>
<button @click="currentTab = 'tab-home'">Home</button>
<button @click="currentTab = 'tab-posts'">Posts</button>
<button @click="currentTab = 'tab-archive'">Archive</button>

<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
</div>
</template>

<script>
import TabHome from './TabHome.vue';
import TabPosts from './TabPosts.vue';
import TabArchive from './TabArchive.vue';

export default {
components: {
'tab-home': TabHome,
'tab-posts': TabPosts,
'tab-archive': TabArchive
},
data() {
return {
currentTab: 'tab-home'
}
}
}
</script>

The <keep-alive> wrapper caches the inactive components, preserving their state rather than destroying them.

Lifecycle Hooks with Keep-Alive

When using <keep-alive>, components get access to two additional lifecycle hooks:

  • activated: Called when a kept-alive component is activated
  • deactivated: Called when a kept-alive component is deactivated

Here's how to use them:

html
<script>
export default {
name: 'TabPosts',
data() {
return {
posts: []
}
},
activated() {
console.log('TabPosts component activated');
// Fetch new posts or refresh data when component is shown
},
deactivated() {
console.log('TabPosts component deactivated');
// Perhaps save some state or clean up resources
}
}
</script>

Advanced Keep-Alive Usage

You can control which components should be cached by keep-alive using the include and exclude props:

html
<!-- Only cache components with names "TabHome" and "TabPosts" -->
<keep-alive include="TabHome,TabPosts">
<component :is="currentTab"></component>
</keep-alive>

<!-- Don't cache the TabArchive component -->
<keep-alive exclude="TabArchive">
<component :is="currentTab"></component>
</keep-alive>

You can also use regular expressions:

html
<!-- Only cache components whose names start with "Tab" -->
<keep-alive :include="/^Tab/">
<component :is="currentTab"></component>
</keep-alive>

Using Dynamic Components with Async Components

For better performance, especially in larger applications, you can combine dynamic components with async components:

html
<template>
<div>
<button @click="currentView = 'user-dashboard'">Dashboard</button>
<button @click="currentView = 'user-profile'">Profile</button>
<button @click="currentView = 'user-settings'">Settings</button>

<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
</template>

<script>
export default {
components: {
'user-dashboard': () => import('./UserDashboard.vue'),
'user-profile': () => import('./UserProfile.vue'),
'user-settings': () => import('./UserSettings.vue')
},
data() {
return {
currentView: 'user-dashboard'
}
}
}
</script>

This approach will only load the component when it's needed, which can significantly improve initial load time for applications with many components.

Practical Example: Tabbed Interface

Let's implement a practical tabbed interface using dynamic components:

html
<template>
<div class="tabs-container">
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
@click="currentTab = tab.component"
:class="{ active: currentTab === tab.component }"
>
{{ tab.name }}
</button>
</div>

<div class="tab-content">
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
</div>
</div>
</template>

<script>
import ProductDetails from './ProductDetails.vue';
import ProductReviews from './ProductReviews.vue';
import ProductSpecifications from './ProductSpecifications.vue';

export default {
components: {
ProductDetails,
ProductReviews,
ProductSpecifications
},
data() {
return {
currentTab: 'ProductDetails',
tabs: [
{ name: 'Details', component: 'ProductDetails' },
{ name: 'Reviews', component: 'ProductReviews' },
{ name: 'Specifications', component: 'ProductSpecifications' }
]
}
}
}
</script>

<style scoped>
.tabs-container {
border: 1px solid #ddd;
border-radius: 4px;
}

.tabs {
display: flex;
border-bottom: 1px solid #ddd;
}

.tabs button {
padding: 10px 15px;
background: none;
border: none;
cursor: pointer;
}

.tabs button.active {
border-bottom: 2px solid #42b883;
font-weight: bold;
}

.tab-content {
padding: 15px;
}
</style>

Practical Example: Multi-Step Form

Dynamic components are perfect for creating multi-step forms:

html
<template>
<div class="form-wizard">
<div class="steps">
<div
v-for="(step, index) in steps"
:key="index"
:class="['step', { 'active': currentStep === index }]"
>
Step {{ index + 1 }}
</div>
</div>

<div class="form-container">
<keep-alive>
<component
:is="steps[currentStep]"
@next-step="nextStep"
@prev-step="prevStep"
v-bind="formData"
@update="updateFormData"
></component>
</keep-alive>
</div>
</div>
</template>

<script>
import PersonalInfo from './steps/PersonalInfo.vue';
import AddressDetails from './steps/AddressDetails.vue';
import PaymentInfo from './steps/PaymentInfo.vue';
import OrderConfirmation from './steps/OrderConfirmation.vue';

export default {
components: {
PersonalInfo,
AddressDetails,
PaymentInfo,
OrderConfirmation
},
data() {
return {
currentStep: 0,
steps: ['PersonalInfo', 'AddressDetails', 'PaymentInfo', 'OrderConfirmation'],
formData: {
name: '',
email: '',
address: '',
city: '',
cardNumber: '',
// other form fields...
}
}
},
methods: {
nextStep() {
if (this.currentStep < this.steps.length - 1) {
this.currentStep++;
}
},
prevStep() {
if (this.currentStep > 0) {
this.currentStep--;
}
},
updateFormData(data) {
this.formData = { ...this.formData, ...data };
}
}
}
</script>

<style scoped>
.form-wizard {
max-width: 800px;
margin: 0 auto;
}

.steps {
display: flex;
margin-bottom: 20px;
}

.step {
flex: 1;
text-align: center;
padding: 10px;
background: #f0f0f0;
}

.step.active {
background: #42b883;
color: white;
}

.form-container {
border: 1px solid #ddd;
padding: 20px;
border-radius: 4px;
}
</style>

Each step component would receive form data from the parent and emit events to move between steps:

html
<!-- PersonalInfo.vue -->
<template>
<div>
<h2>Personal Information</h2>
<div class="form-group">
<label>Name</label>
<input type="text" v-model="localName" />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" v-model="localEmail" />
</div>

<div class="buttons">
<button @click="saveAndContinue">Next</button>
</div>
</div>
</template>

<script>
export default {
props: ['name', 'email'],
data() {
return {
localName: this.name,
localEmail: this.email
}
},
methods: {
saveAndContinue() {
this.$emit('update', { name: this.localName, email: this.localEmail });
this.$emit('next-step');
}
}
}
</script>

Dynamic Components with Vue 3 Composition API

If you're using Vue 3 with the Composition API, you can implement dynamic components like this:

html
<template>
<div>
<button @click="currentView = 'ViewA'">View A</button>
<button @click="currentView = 'ViewB'">View B</button>

<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
</template>

<script>
import { ref } from 'vue';
import ViewA from './ViewA.vue';
import ViewB from './ViewB.vue';

export default {
components: { ViewA, ViewB },
setup() {
const currentView = ref('ViewA');

return {
currentView
};
}
}
</script>

Best Practices for Dynamic Components

When working with dynamic components:

  1. Use keep-alive when appropriate: If you need to preserve component state, use keep-alive, but be aware it increases memory usage.

  2. Utilize lazy loading for large components: For better performance, use async components with dynamic imports.

  3. Set a key attribute: When dynamically switching between the same component type but with different props, use the key attribute to force re-rendering:

    html
    <component :is="currentComponent" :key="componentKey"></component>
  4. Watch for memory leaks: When using keep-alive, be careful not to cache too many heavy components that might not be used again.

  5. Use the appropriate lifecycle hooks: For components wrapped in keep-alive, remember to use activated and deactivated hooks for logic that should run when components are shown or hidden.

Summary

Dynamic components in Vue.js provide a powerful way to create flexible, interactive UIs. They allow you to:

  • Switch between different components at a single mount point
  • Preserve component state using keep-alive
  • Create interactive UIs like tabbed interfaces, multi-step forms, and content toggles
  • Optimize performance through lazy loading with async components

Whether you're building simple toggle interfaces or complex multi-step workflows, dynamic components offer a clean, declarative way to manage component rendering based on the application state.

Additional Resources

  • Try extending the tabbed interface example to include animations when switching tabs
  • Implement a wizard-style form with validation for each step
  • Create a dashboard with multiple view modes using dynamic components
  • Explore Vue's transition system to add animations when switching between dynamic components

Exercises

  1. Create a simple content switcher with three different views
  2. Build a tabbed interface that preserves form input across tab changes
  3. Implement a "view mode" switcher (e.g., grid view vs. list view)
  4. Create a slideshow component that cycles through different content components
  5. Build a multi-step form with validation and the ability to navigate between steps

By mastering dynamic components, you'll be able to create more interactive and flexible user interfaces in your Vue applications!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)