In this article, we’ll look at how to define and use dynamic and async components in Vue.js.
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>
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.
Example 2:
const AsyncComponent = () => ({
// The component to load (should be a Promise)
component: import('./MyComponent.vue'),
// A component to use while the async component is loading
loading: LoadingComponent,
// A component to use if the load fails
error: ErrorComponent,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000
})
Using with Suspense
Async components are suspensible by default. This means if it has a <Suspense>
in the parent chain, it will be treated as an async dependency of that <Suspense>
. In this case, the loading state will be controlled by the <Suspense>
, and the component's own loading, error, delay and timeout options will be ignored.
The async component can opt-out of Suspense
control and let the component always control its own loading state by specifying suspensible: false
in its options.