Vuejs Dynamic Slots
Would you like to buy me a āļø instead?
See the Pen Dynamic components: without keep-alive by Vue on CodePen. You'll notice that if you select a post, switch to the Archive tab, then switch back to Posts, it's no longer showing the post you selected. That's because each time you switch to a new tab, Vue creates a new instance of the currentTabComponent. Dynamic properties don't seem to work on the component, so the 'slot' property is getting set to null and it is not being loaded posva changed the title Dynamic slot name binded from v-for directive not working with scoped slots Dynamic slot name bound from v-for directive not working with scoped slots May 5, 2017. Vue has support for slots built in and it is based on the shadow dom. It is a specification that is still in development but worth taking a look at to learn more about how they work, should this development approach become more common.
Vue.js is flexible enough to serve as a tool for either progressively enhancing certain parts of traditional server-side rendered applications or powering large scale single-page applications, and everything in between. If you build complex single-page applications, youāll most likely encounter situations where you need different page layouts for certain parts of your app.
Today weāll take a look at multiple ways of how to handle layouts in Vue.js, and weāll explore the potential up and downsides of the different approaches.
You can find the code for this article on GitHub, and you can browse the final result hosted on Netlify.
The Vue CLI way
Nowadays, I assume that most of you use the awesome Vue CLI to kickstart new Vue.js projects (if you donāt, you absolutely should check it out). So letās take a look at what the Vue CLI is preparing for us.
This is the default approach for structuring a basic layout for a Vue Router powered Vue.js application. It works fine as long as you donāt need different layouts throughout your application. For example, you may have a checkout flow where you donāt want to display a navigation. Or you might have product pages with sidebars and other pages without sidebars and so on.
Letās take a look at how we can enhance the default approach, provided to us by the Vue CLI, to handle cases where we have to display different layouts.
Conditional rendering
The most basic and straightforward approach would be to render certain parts of your layout conditionally. So you might add v-if
directives to certain parts of your layout and toggle the visibility as you wish.
One problem of this approach is, that you have to control the visibility of those elements somewhere in your application. There are multiple ways of doing this and you can read about some of those in one of my recent articles about how to handle global state in Vue.js.
But to be completely honest, Iām not a big fan of this approach. Although it might be the right way to go if you donāt need very complex layouts and you just want to hide some element in certain contexts, this approach can potentially become a maintenance nightmare as your application is growing.
Static layout wrapper components
Next we take a look at how we can use an ordinary component, containing one or multiple slots for the different parts of the layout, as a wrapper for our views.
This approach is used by a lot of people (including me) because it offers a ton of flexibility and it also feels not as dirty as the conditional rendering approach.
In the template section of the src/App.vue
base component, we only render the <router-view>
.
A new src/layouts/LayoutDefault.vue
component now renders the layout for us and it provides a default <slot>
for the content. This is basically the layout for all the regular pages (views) of our application.
The src/Home.vue
component implements the LayoutDefault
wrapper component to wrap its content.
Although, in terms of flexibility, this approach has everything we need, there is one huge downside of wrapping our views in a static layout component: the component is destroyed and re-created every time the route changes. This not only has a negative effect on the performance, because the client has to re-create the layout component (and all the other components nested inside of it) again and again on every route change, but it can also mean you have to fetch certain data, which you use in one of the components of your layout, on every route change.
You can play around with the following demo application to see an example of this in action. Note that the username in the top right will be loaded again and again every time you navigate from one page to another.
So static layout wrapper components are very powerful and flexible, but they also come with a cost. Letās check out if we can come up with an approach which has all the positive characteristics of static wrapper components but none of the negative ones.
Dynamic layout wrapper components
Before we get started, let me say that the component system in Vue.js is very, very powerful. One of those very powerful features of the component system are dynamic components.
In the example above, SomeComponent
is a variable, which can be dynamically assigned to any component and each time you assign a different component, the template renders the new component in the place where you defined the <component>
tag.
We can use dynamic components, to build a very flexible yet performant dynamic layout system.
First of all, letās update our App
base component to prepare it for dynamic layouts. In order to do so, we wrap the <router-view>
tag with a dynamic component <component>
tag. The <component>
tag renders whatever component is defined in the layout
variable.
Because we want our router views to be able to control which layout component is rendered, we define a synchronous layout
property on the <router-view>
tag. This makes it possible to update the layout
property from within our views by emitting a update:layout
event.
The is
property of the dynamic component can also be set to render a regular HTML element. So what we can do is to set the value of layout
to div
by default as a fallback if a view has not defined its own layout. But keep in mind that this means such a view is rendered inside a <div>
if its route is accessed directly but it renders as whichever layout was set by the view before if it is not accessed directly but via following a link in you SPA. So I recommend you to explicitly set a layout in all of your view components.
Above you can see that we donāt wrap the template of our Home
view inside of the LayoutDefault
component anymore, but we load the component and emit it as the new value of the layout
property which weāve defined in the App
base component. This means, as soon as the Home
component is created, the dynamic component wrapping the <router-view>
, which renders the Home
component, is re-rendered to render the component weāve emitted in the created()
hook.
Why is this better than static wrapper components?
At first glance, this might seem like a more complicated version of the static layout wrapper approach. But let me explain why this approach comes with all the benefits of the static component approach but shares none of the potential problems those can have.
The main difference is, that the layout component is not part of the router view component but wraps around it. This means that the layout component is only re-rendered if the view component uses a different layout than the previous view component. So for example, if all of your pages but the login page, use the same layout, the layout is only re-rendered if the user opens the login page, which should be relatively rare.
Next you can see the same demo application as in the static layout wrapper example but now using a dynamic component to load the layout instead. Note that other than in the first example, the name of the currently logged in user in the top right, is not fetched again and again on every route change because only the router view is re-rendered.
Building a renderless dynamic layout component
Renderless components are awesome. You can read more about them in one of my previous articles about how to build renderless declarative data fetching components. In our case, we can utilize the power of renderless components to make it easier and more comfortable to use dynamic layout components in our views. So letās refactor our code.
Vuejs Dynamic Slots App
Similar to the static layout wrapper approach, we wrap the template of our view in a wrapper component again. But this time, the wrapper component is a renderless component which does not render any markup itself.
As you can see above, weāve moved the code for emitting the layout component we want to use, into a new LayoutDefaultDynamic
renderless component. Weāre doing so by calling the $emit
method on the $parent
(this is a reference to the view component which is utilizing the dynamic layout component). Usually reaching for the $parent
is kind of an anti pattern but in this case itās ok because the LayoutDefaultDynamic
component must be used in the context of a view component, so we can be sure that $parent
always is a reference to a view component.
Refactoring our code to utilize the renderless components technique, makes using dynamic layouts much more intuitive.
Do you want to learn more about advanced Vue.js techniques?
Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.
Improve bundle size with dynamic imports
Depending on how well your JavaScript code is optimized by webpack and depending on the structure of your application (e.g. if you have a lot of different layouts or one default layout and some rarely used special layouts), with this approach, you might end up in a situation where the same layouts are loaded again and again on every route change. We can work against that by dynamically importing the layout component only in case it has not been loaded and registered before.
Vuejs Dynamic Slot Content
As Iāve already said, this approach is not necessarily better than the previous way of not using dynamic imports. It highly depends on your application and how well webpack optimizes your bundles. Iād recommend you to test both approaches and settle for the one which provides better results in terms of performance, or, if the differences are negligible, whichever you like better.
Like What You Read?
Follow me to get my latest Vue.js articles.
Wrapping it up
When building large scale single-page applications, it is almost inevitable that you need a robust layout system. Using dynamic rendering can be a quick fix but it can become a maintenance hell as complexity is growing and requirements are changing. Static wrapper layouts can be problematic in terms of rendering performance and also can lead to larger bundle sizes.
By using dynamic renderless layout components we can achieve the same flexibility and functionality as with static wrapper layouts but without forcing the client to re-render the complete layout on every route change and with the potential to improve bundle sizes thanks to dynamic imports.