Dynamic and Asynchronous Vue Components: The What & How
Nyior Clement Jr.
Posted on September 10, 2023
It’s asking the right questions that count, they say. So let’s exploit that ancient wisdom and kick things off here with the questions that matter :)
The image above shows a user interface with two tabs: JobDescription
and ApplicationForm
. We want to explore how to create tabbed interfaces like this one using Vue.
It's easy to think it's simple – just create <JobDescription />
and <ApplicationForm />
components and render them to the DOM, as shown in the snippet below. But is it really that straightforward?"
<template>
<JobDescription />
<ApplicationForm />
</template>
Well, that's not quite right. If we render our components that way, both of them would appear at the same time. What we need is to have a single component appear dynamically based on the selected tab.
There's some dynamic behaviour we need: while we anticipate the user's selection of one of two components, the precise choice remains uncertain. What we want is a placeholder, a receptacle if you will, where Vue could insert components based on the selected tab in real-time.
Using vue-router here is not ideal. We do not want our URL changing when users switch between tabs. They are tabs, not pages.
So we pose the question, how can this type of dynamic behaviour be achieved in Vue?
I will show you how, but first, let's take a quick detour...
Suppose we've managed to learn how to dynamically render components in Vue. With this in mind, let’s take our analysis a step further. You may have already observed that the 'ApplicationForm' tab includes a basic job application form with a field for entering the full names of applicants.
Typically, if you enter your name in the 'ApplicationForm' tab's input field, then switch to the 'JobDescription' tab, and come back to your form, you'll notice that what you typed in the input field is gone. The image below illustrates this behavior:"
If you think about how Vue renders components, you’d see that this type of behaviour is expected. Every time we switch from a component, Vue un-mounts that component.
In our scenario, when transitioning from 'ApplicationForm' to 'JobDescription,' Vue un-mounts the former from the DOM and then mounts the latter. Any input in the field is lost because Vue needs to completely re-render the 'ApplicationForm' component when we switch back to it.
Could we potentially retain the state of a tab we've just left, you may ask? After all, we wouldn't want our users to have to re-enter their information from the beginning whenever they switch to a different tab, would we?
This brings us to our second question: How can we persist the state of components in our tabs scenario? What we want is an effect like the one shown in the image below:
Again, let’s assume we’ve somehow figured out how to persist the state of our components, and in our quest for perfection, we want to do one more thing: some performance optimization.
You see, even though Vue dynamically renders our components one at a time, depending on what tab is selected, whenever that page is visited, Vue would load the code for all the tabs on that page. Imagine if we had thousands of tabs we could switch between - not ideal, but a little bit of exaggeration could help here.
The first time that page is visited the code for all the thousands of tabs would be loaded from the server. I put this to you: Especially since Vue would only be rendering one tab at a time to the DOM, is grabbing the code for all the tab components upfront really necessary? What potential performance drawbacks might arise from this approach? Take a moment and reflect - or maybe you don't need to.
If your spidey-sense is already telling you there’s got to be a better way, then you’re absolutely right. What we could do is have Vue load the code for a tab only when it’s selected. So each time the page is visited only the code for the default tab is loaded. For other tabs, they’d only be loaded when visited. If you want to associate this technique with a name, just call it lazy loading.
This brings us to our third and final question: How can we implement lazy component loading in Vue?
In the subsequent sections, this piece would demonstrate how you could address the first two questions we raised with Vue Dynamic Components and how you could implement lazy loading with Asynchronous Components.
Dynamic Components
As mentioned just now, we could implement tabbed interfaces where users could switch between tabs and have each tab persist its state with dynamic components.
Normally, to add a component to a template, we’d add it by name to the template like in the snippet below:
<template>
<ApplicationForm />
</template>
That way, we are statically adding our component to that template and by extension statically rendering it to the DOM. What makes a component in Vue dynamic is the fact that instead of statically adding the component to the template we’d have a placeholder of some sort where Vue could insert one of many components after some action happens. A placeholder?
Well yes. Vue has an inbuilt <component>
element with the special is
attribute:
<component :is="currentTab"></component>
The <component>
element in the above is the placeholder, and the component rendered in that frame would change once the component CurrentTab
is tied to changes. To make this work with our tabs scenario, you’d need to have a list of components defined in the script section of your component. At each point, one of the components in your list has to be mapped to currentTab
. The <component>
frame would then render whatever component is tied to currentTab
. Way too abstract? Okay, let’s look at how we could do this with our ApplicationForm
and JobDescription
tabs.
The snippet below shows what the script section of our component would look like:
<script>
import JobDescription from "@/components/JD.vue"
import ApplicationForm from "@/components/ApplicationForm.vue"
export default {
components: {
JobDescription,
ApplicationForm
},
data() {
return {
currentTab: 'JobDescription',
tabs: [ 'JobDescription','ApplicationForm']
}
}
}
</script>
In the snippet above, we initialized tabs
array with the components we imported. We then set JobDescription
to be our default currentTab
. What this means is that each time that page is visited, the JobDescription
tab is what will be shown. But how do we update our currentTab
to ApplicationForm
when users switch to that tab in the UI? The update is done in the <template>
section of our component as shown in the snippet below:
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
@click="currentTab = tab"
>
{{ tab }}
</button>
<KeepAlive>
<component :is="currentTab" class="tab"></component>
</KeepAlive>
</div>
</template>
In the snippet above we are using @click
to update currentTab
to the tab being clicked. One other thing you might have noticed is how the <component>
element is wrapped by a KeepAlive
component. The keepAlive
is an inbuilt component that preserves the state of our component. In our case with the keepAlive
around, when switching from the ApplicationForm
component, whatever we typed in the input field would still be there when we return.
Two of our concerns had been addressed. What’s left for us now is exploring how we could load components on-demand. We previously tagged this lazy loading. Let’s quickly look at how we could do this.
Asynchronous Components
Let’s load our ApplicationForm
component on-demand. Note that you could apply this to just any component. To load our component in question asynchronously, all we have to do is update how we are registering the component. See the snippet below:
<script>
import JobDescription from "@/components/JD.vue"
// import ApplicationForm from "@/components/ApplicationForm.vue"
import { defineAsyncComponent } from 'vue'
export default {
components: {
JobDescription,
ApplicationForm: defineAsyncComponent(() =>
import('@/components/ApplicationForm.vue')
)
},
data() {
return {
currentTab: 'JobDescription',
tabs: [ 'JobDescription','ApplicationForm']
}
}
}
</script>
Essentially, the only change we made is in using vue3’s defineAsyncComponent
feature to register our ApplicationForm
as an asynchronous component. With that, the ApplicationForm
component will be loaded on demand.
Conclusion
In this piece, we’ve seen how we could render components to the DOM dynamically in Vue. We’ve also seen how the inbuilt <KeepAlive>
component in Vue could help us persist the states of our component. And lastly, we explored how we could load components on demand to optimize for speed.
That’s all I have for you :)
If you want to share your thoughts on this tutorial with me or simply just connect, you can find/follow me on Github, Linkedin, or Twitter.
Posted on September 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024