lobo_tuerto's notes
Dynamic and async components made easy with Vue.js
Very easy, very, very easy
Published on
2018 / 02 / 11
Updated on
2019 / 09 / 11
Tags
tutorialfrontendvue.jscomponentsdynamicasyncwebpack
Versions
vue.js:2vue-cli:3
v1.1.2

§ Interactive demo

This is a mini Vue.js app with its own router and mount point inside a Markdown component.

The Playground view is loaded asynchronously after you click on the Playground button.
If you want to see that in action, just open your network tab on your DevTools, then click on the playground button below.

The basics for the interactive demo are explained below.


I think this post will be most useful to advanced Angular/AngularJS developers that are still pondering what’s all the fuss about Vue.js —just like I was just a few weeks ago.

Nonetheless, I have included step-by-step instructions that will help beginners too.

So, in this —opinionated— tutorial, I’m hoping you’ll:

  • Realize just how ludicrously easy is to setup on-demand / lazy-load for Vue.js components.
  • Show you how to combine dynamic components + async loading for maximum effect!

§ Use cases

These are the use cases we will review in this tutorial:

  • The app router should only load code for sections of the app you actually visit.
  • You should be able to dynamically add/remove/swap components on a page.
  • Inside a section, the app should only load code for components that are actually rendered.
    A component might be declared an available for rendering, but its code should only load if you display it.

§ Prerequisites

You can setup a new Vue.js app following this guide.

§ Asynchronous route loading

To showcase this feature let’s create a new SFC (Single File Component).

Don’t you just love .vue files?
They are like little packages composed of a template, a script and a style section in the same file.

This allows for very powerful modularization and code reuse.

Create a src/views/Playground.vue file with this content:

<template lang="pug">
v-container
  h1 Welcome to the component playground
</template>

Taking advantage of Webpack’s code chunking we can make our app’s initial rendering very fast by only loading the code we need at the beginning and loading other parts on-demand.

Let’s make our new Playground.vue component to load asynchronously.

Open the src/router.js file and modify it to look like this:

import Vue from 'vue'
import Router from 'vue-router'

import Home from './views/Home.vue'


Vue.use(Router)


export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    },
    {
      path: '/playground',
      name: 'playground',
      component: () => import('@/views/Playground')
    }
  ]
})

To see the async loading in action, open the browser console (press F12) go to the Network tab and visit http://localhost:8080/

Now visit http://localhost:8080/#/playground and observe how it makes a request for a 0.js file when you change the URL.

The good thing? The app will only make that request once, then it’ll be cached afterwards!

This is the line of code that makes this possible:

component: () => import('@/views/Playground')

How hard is this for async loading of router level components?
Pretty damn easy I bet! :)

§ Dynamic component rendering

Another thing that is very easy to do in Vue.js is rendering components dynamically.
Just have a look for yourself and judge.

§ Defining new components

Let’s create three components to use on the Playground:

  1. src/components/SimpleButton.vue:

    <template lang="pug">
    v-btn.mr-4.mb-4 Simple button
    </template>
    
  2. src/components/SimpleCard.vue:

    <template lang="pug">
    v-card.elevation-4.my-4
      v-card-text
        strong Async loaded card
    </template>
    
  3. src/components/SimpleSwitch.vue:

    <template lang="pug">
    v-switch(hide-details label="Simple switch").my-4
    </template>
    

§ Static rendering

If you want to see your new components in action, modify the src/views/Playground.vue file to look like this:

<template lang="pug">
v-container
  h1 Welcome to the component playground

  simple-button
  simple-card
  simple-switch
</template>


<script>
import SimpleButton from '@/components/SimpleButton'
import SimpleCard from '@/components/SimpleCard'
import SimpleSwitch from '@/components/SimpleSwitch'

export default {
  components: { SimpleButton, SimpleCard, SimpleSwitch }
}
</script>

Then visit: http://localhost:8080/#/playground

§ Dynamic rendering

What we’ll do in this section is present a <select> input as means to pick a component from a list and then display it.

This can be accomplished through the powerful <component> element.

Modify the Playground.vue file to look like this:

<template lang="pug">
v-container
  h1 Welcome to the component playground

  select(v-model="selectedComponent")
    option(
      v-for="(item, index) in componentList"
      :key="index"
      :value="item.component"
    ) {{ item.label }}

  hr
  .dynamic-component
    component(:is="selectedComponent")
</template>


<script>
import SimpleButton from '@/components/SimpleButton'
import SimpleCard from '@/components/SimpleCard'
import SimpleSwitch from '@/components/SimpleSwitch'

export default {
  data () {
    return {
      componentList: [
        {
          component: SimpleButton,
          label: 'A button'
        },
        {
          component: SimpleCard,
          label: 'A card'
        },
        {
          component: SimpleSwitch,
          label: 'A switch'
        }
      ],
      selectedComponent: null
    }
  }
}
</script>


<style lang="sass" scoped>
.dynamic-component
  border: 1px dashed red
  display: inline-block
  padding: 1em
</style>

Visit: http://localhost:8080/#/playground and use the select input.
Awesome, right?

§ Async loading + dynamic rendering

What do you think would take to enable async loading for the SimpleCard component above?

Well, you’ll only need to change Playground.vue to this:

<template lang="pug">
v-container
  h1 Welcome to the component playground

  select(v-model="selectedComponent")
    option(
      v-for="(item, index) in componentList"
      :key="index"
      :value="item.component"
    ) {{ item.label }}

  hr
  .dynamic-component
    component(:is="selectedComponent")
</template>


<script>
import SimpleButton from '@/components/SimpleButton'
// Line below is commented since we will be loading it asynchronously
// import SimpleCard from '@/components/SimpleCard'
import SimpleSwitch from '@/components/SimpleSwitch'

export default {
  data () {
    return {
      componentList: [
        {
          component: SimpleButton,
          label: 'A button'
        },
        {
          // Async loading!
          component: () => import('@/components/SimpleCard'),
          label: 'A card'
        },
        {
          component: SimpleSwitch,
          label: 'A switch'
        }
      ],
      selectedComponent: null
    }
  }
}
</script>


<style lang="sass" scoped>
.dynamic-component
  border: 1px dashed red
  display: inline-block
  padding: 1em
</style>

Very easy, don’t you think?

You can verify it’s loading asynchronously by watching the Network tab in your browser console.
Then select the A card option.
At that moment a request will be made to retrieve that component’s code!

§ Staying alive

The astute one might have noticed that whatever state you set on the SimpleSwitch component is lost when you switch to another component.

If you want to preserve on memory what’s on these dynamic components, all you have to do is surround the <component> element with <keep-alive> tags like this:

<template lang="pug">
v-container
  h1 Welcome to the component playground

  select(v-model="selectedComponent")
    option(
      v-for="(item, index) in componentList"
      :key="index"
      :value="item.component"
    ) {{ item.label }}

  hr
  .dynamic-component
    keep-alive
      component(:is="selectedComponent")
</template>

Go try it out! Pick A switch, switch it on, then pick something else, then return to A switch and you’ll see it preserved its state. Awesome!

§ Some snarky comments

Do you know what other thing is a testament to Vue’s power and flexibility?

That Vuetify is a more advanced, featureful and complete implementation of the Material Design Guidelines than Angular Material itself.

Go figure!

If you are an Angular developer, compare the stuff we did above with how you’d do lazy loading in Angular.

FINIS
Got comments or feedback?
Follow me on twitter
v1.1.2