I'm writing this tutorial to bring some well-deserved attention to Dynamic Components in Vue.js. I extensively use them when building simple single page applications where including a full-blown routing solution can be unnecessary.
Dynamic components in Vue allow you to specify a single mounting point where you can dynamically switch between components in your application. Throughout this tutorial, we'll be taking a look at a couple of examples to see how they work in practice. We will start off by creating a dynamic component, exploring the keep-alive
parameter, and finally applying some transitions to our components as the user switches between them.
Creating a Dynamic Component
For the example we're going to be using in this tutorial, assume we are building a simple admin dashboard for a blog. We would like 2 pages:
- one to manage the existing blog posts (edit + delete)
- one to create a new blog post
Each of those pages would translate into a component, which we will create shortly. For now, let's start off by creating the root Vue instance for our app and mounting it to the div
tag with an ID of #app
:
new Vue({
el: '#app'
})
and the markup for our dashboard will look like so:
<div class="header clearfix">
<nav>
<ul class="nav nav-pills pull-right">
<li role="presentation">
<a href="#">Manage Posts</a>
</li>
<li role="presentation">
<a href="#">Create Post</a>
</li>
</ul>
</nav>
<h3 class="text-muted">Admin Panel</h3>
</div>
<div class="container">
<!-- render the currently active component/page here -->
</div>
This will give us a very simple navigation bar with 2 links, Manage Posts and Create Post, like so:
Now that we have the structure in place for our application, let's create the 2 components: one for managing existing posts and one creating a new post. If you don't know how to create a component in Vue, I'd suggest having a look at this tutorial first.
For managing the posts we will create a manage-posts
component with some fake posts like so:
Vue.component('manage-posts', {
template: '#manage-template',
data: function() {
return {
posts: [
'Vue.js: The Basics',
'Vue.js Components',
'Server Side Rendering with Vue',
'Vue + Firebase'
]
}
}
})
and the template for the manage-posts
component will be quite simple as well. It will render the posts array with a delete and edit button:
<template id="manage-template">
<div>
<h1>Manage Posts</h1>
<div class="list-group">
<a v-for="post in posts" href="#" class="list-group-item clearfix">
{{ post }}
<span class="pull-right">
<button class="btn btn-xs btn-info">
<span class="glyphicon glyphicon-edit"></span>
</button>
<button class="btn btn-xs btn-warning">
<span class="glyphicon glyphicon-trash"></span>
</button>
</span>
</a>
</div>
</div>
</template>
So far we've created a root Vue instance which has a simple navigation bar with 2 links and a manage-posts
component. Let's define the create-post
component then wire everything up:
Vue.component('create-post', {
template: '#create-template'
})
and it's template:
<template id="create-template">
<div>
<h1>Create Post</h1>
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Post title</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="Post title">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Post body</label>
<div class="col-sm-10">
<textarea class="form-control" rows="5"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Create</button>
</div>
</div>
</form>
</div>
</template>
With both components defined and a Vue instance created, let's take a look at how we can wire everything up as dynamic components.
When a user clicks on the Manage Posts link, we want to tell Vue to render the manage-posts
component. Similarly, when a user clicks the Create Post link, we want the create-post
component to be rendered.
To do this we can use the special <component>
element as a mounting point in our root Vue instance and specify which component we wish to render using the is
attribute. Going back to the markup for our dashboard, we can render the manage-posts
component like this:
<div class="container">
<!-- render the currently active component/page here -->
<component is="manage-posts"></component>
</div>
Together, our dashboard with the navigation bar and manage-posts
component looks like this:
and to render the create-post
component we can simple change the value of the is
attribute:
<component is="create-post"></component>
However, when it comes to actually navigating our single page application (SPA), we will need to make this truly dynamic so that when a use clicks on the Manage Posts link it switches to the manage-posts
component, and when they click on the Create Post link, it renders the create-post
component.
This is quite simple with Vue's handy v-bind
directive. We can go back to our root Vue instance and define a string called currentView
in the data object, which will keep track of the currently active component:
new Vue({
el: '#app',
data: {
currentView: 'manage-posts'
}
})
We can now bind the currentView
property to the is
attribute using the shorthand for the v-bind
directive:
<div class="container">
<!-- render the currently active component/page here -->
<component :is="currentView"></component>
</div>
Finally, we just need to update the currentView
property on our data each time a user clicks the Manage Posts or Create Post links so that the selected component is rendered dynamically. Using the shorthand for v-on
(the @
symbol), we can listen for click events on the links and update the currentView
appropriately:
...
<li role="presentation">
<a href="#" @click="currentView='manage-posts'">Manage Posts</a>
</li>
<li role="presentation">
<a href="#" @click="currentView='create-post'">Create Post</a>
</li>
...
Here's a Fiddle with the complete code that you can play around with to see how everything fits together:
The keep-alive Element
Now that we've covered how to create dynamic components, let's take a second to talk about the very important <keep-alive>
tag and how it can help us.
Every time you click on a link in our admin dashboard to switch between creating a post or managing them, the previous component is torn down and the selected one re-rendered from scratch.
This would cause the component to lose all it's local state and have to make any API calls it needs to fetch the posts from the database and then re-render them. To avoid this, you can wrap the component in the <keep-alive>
tag to keep the switched-out components alive:
<div class="container">
<!-- render the currently active component/page here -->
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
Vue.js will intelligently cache the inactive components for you! You can try this yourself with the Fiddle below. To prove this, try typing something in the Create Post component, then switch over to the Manage Posts component, and back to Create Post again. You should see the text that you typed still there since the component (including it's state) was cached.
This is especially useful if your component makes any costly API calls or needs to render large amounts of data each time.
Transition Between Components
To add some finishing touches to the admin dashboard, let's apply some transitions as the user switches between the Create and Manage components.
On the component mounting point, we can add a transition parameter where we'll just fade out of the current component and fade in to the newly selected component:
<div class="container">
<component :is="currentView" transition="fade" transition-mode="out-in"></component>
</div>
We can now define what the fade
transition looks like in CSS:
.fade-transition {
transition: opacity 0.2s ease;
}
.fade-enter, .fade-leave {
opacity: 0;
}
And that's all you need to apply a transition between your component switches! You can try it out in the Fiddle below:
You may have noticed the transition-mode="out-in"
we added as well. This basically tells Vue that we want to fade out the previous component then fade in the new one. Alternatively, you can change to transition-mode="in-out"
to first fade in the new component and then fade out the old one.
If you don't specify a transition-mode
, the fading in and out transitions for both components will occur simultaneously, which wont look too great!
Wrapping Up
Hopefully this tutorial gave you a practical look at how you can use dynamic components to build a SPA with ease thanks to Vue's <component>
element and it's simple data binding syntax.
Dynamic components are by no means a replacement for routing solutions since vue-router
offers a ton of useful features that I'd encourage you to have a look at, however, they suffice for simple use cases. Plus, you can seamlessly switch to vue-router
as your application grows in size and demands more features since vue-router
just relies on components, which you've already been using all along!