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:
<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:
- We import three components
- We register them in the
components
option - We use
data
to track which component should be currently displayed - We use the
<component :is="currentComponent">
element to render the currently selected component - 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:
<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 activateddeactivated
: Called when a kept-alive component is deactivated
Here's how to use them:
<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:
<!-- 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:
<!-- 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:
<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:
<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:
<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:
<!-- 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:
<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:
-
Use
keep-alive
when appropriate: If you need to preserve component state, usekeep-alive
, but be aware it increases memory usage. -
Utilize lazy loading for large components: For better performance, use async components with dynamic imports.
-
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>
-
Watch for memory leaks: When using
keep-alive
, be careful not to cache too many heavy components that might not be used again. -
Use the appropriate lifecycle hooks: For components wrapped in
keep-alive
, remember to useactivated
anddeactivated
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
- Create a simple content switcher with three different views
- Build a tabbed interface that preserves form input across tab changes
- Implement a "view mode" switcher (e.g., grid view vs. list view)
- Create a slideshow component that cycles through different content components
- 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! :)