Categories
JavaScript Vue

Vue Components — Dynamic and Async Components

Spread the love

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to define and use dynamic and async components.

keep-alive with Dynamic Components

We sometimes want to maintain the state or avoid re-rendering for performance reasons when we switch between components dynamically.

For example, we can use it as follows to keep the states of components when switching between them:

src/index.html :

Vue.component("post", {  
  data() {  
    return {  
      showDetails: false  
    };  
  },  
  template: `  
    <div>  
      <h1>Title</h1>  
      <button @click='showDetails = !showDetails'>  
        Toggle Details  
      </button>  
      <div v-if='showDetails'>  
        Lorem ipsum dolor sit amet.  
      </div>  
    </div>  
  `  
});

Vue.component("archive", {  
  data() {  
    return {  
      showDetails: false  
    };  
  },  
  template: `  
    <div>  
      <h1>Archive</h1>  
    </div>  
  `  
});

new Vue({  
  el: "#app",  
  data: {  
    tabName: "post"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click'tabName = "post"'>Post</button>  
      <button @click='tabName = "archive"'>Archive</button>  
      <keep-alive>  
        <component v-bind:is="tabName"></component>  
      </keep-alive>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we click Toggle Detail to show the text in the Post tab, we’ll see that it’ll still be shown after switching to the Archive tab and back.

This is because we wrapped keep-alive around our component element.

keep-alive requires that the components being switched between to all have names.

Async Components

We can create components that are loaded from the server only when it’s needed with async components.

To do this, instead of passing in an object as the second argument, we pass in a function that returns a promise instead. For example, we can write the following:

src/index.js :

Vue.component("async-component", (resolve, reject) => {  
  setTimeout(() => {  
    resolve({  
      template: "<div>Async component</div>"  
    });  
  }, 1000);  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <async-component></async-component>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we should see ‘Async component’ shown on the screen after 1 second.

We see that the function above takes a resolve function from the parameter and calls it when we want to retrieve the definition from the server.

To indicate loading has failed, we can call reject(reason) to do that.

Another example would be using it with Webpack code-splitting as follows:

Vue.component('async-webpack', function (resolve) {  
  require(['./async-component'], resolve)  
})

We can also use the import function to import a component as follows:

Vue.component(  
  'async-webpack',  
  () => import('./async-component')  
)

If we want to register a component locally, we can write:

new Vue({  
  // ...  
  components: {  
    'component': () => import('./async-component')  
  }  
})

Handling Loading State

We can return a promise that has an object to referencing components for loading and error.

For example, we can use it with an app that’s generated by the Vue CLI by running npx vue create app where app is the app name. Then we can select the default choices within the wizard.

Then we write the code as follows:

App.vue :

<template>  
  <div id="app">  
    <AsyncComponent/>  
  </div>  
</template>

<script>  
import Loading from "./components/Loading";  
import Error from "./components/Error";  
import HelloWord from "./components/HelloWorld";

const AsyncComponent = () => ({  
  component: new Promise(resolve => {  
    setTimeout(() => {  
      resolve(HelloWord);  
    }, 1000);  
  }),  
  loading: Loading,  
  error: Error,  
  delay: 0,  
  timeout: 3000  
});

export default {  
  name: "App",  
  components: {  
    AsyncComponent  
  }  
};  
</script>

<style>  
</style>

components/HelloWorld.vue :

<template>  
  <div>Hello</div>  
</template>

components/Error.vue :

<template>  
  <div>Error</div>  
</template>

component/Loading.vue :

<template>  
  <div>Loading</div>  
</template>

In App.vue , the following code:

component: new Promise(resolve => {  
    setTimeout(() => {  
      resolve(HelloWord);  
    }, 1000);  
  })

delays loading of the HelloWorld component for a second. delay is the number of milliseconds before the loading component is shown.

timeout is the number of milliseconds until Vue stops trying to load the component.

Therefore, we’ll see Loading from the Loading component as the HelloWorld component is loading.

This kind of component definition is available since Vue 2.3.0. Vue Router 2.4.0 should be used if we want to use the above syntax for route components.

Conclusion

We can use keep-alive to keep the current state of the component as it’s being rendered dynamically with the component element.

We can define async components with Vue.component by passing in a function that returns a promise.

In Vue 2.3.0 or later, we can define an async component by creating a function that returns an object that has the promise that resolves to the component as the component that’s rendered.

We can also specify the Loading and Error components in addition to the delay for loading the Loading component and timeout until Vue stops trying to load the component.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *