Reputation: 2425
I have many components, i want to import then on demand. i have a drop down which actually contains components list, that what to load. I tried this example
<component :is="componentLoader"></component>
in script
componentLoader () {
return () => import('@/components/testDynamic')
}
testDynamic is a component name(For now i am trying with static component).
Getting this error
GET http://localhost:8080/0.js net::ERR_ABORTED 404
[Vue warn]: Failed to resolve async component: function () {
return __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, "./src/components/testDynamic.vue"));
}
Reason: Error: Loading chunk 0 failed.
How to fix this? am i doing any thing wrong? or is there any other way to import components dynamically?
Upvotes: 35
Views: 44085
Reputation: 1720
Here's an expansion on the answer by Brian Lee, with additional UX coverage, and a Webpack optimization:
<template>
<AssetMedia
v-if="assetToEdit.typeOfAsset === 'social'"
:key="assetToEdit.id"
:assetOfInterest="assetToEdit" />
</template>
<script>
// Components.
import AppErrorComponent from '../../application/Error'
import AppLoadingComponent from '../../application/Loading'
export default {
name: 'AssetEdit',
components: {
AssetMedia: () => ({
component: import(/* webpackChunkName: "AssetMedia" */ './features/AssetMedia'),
loading: AppLoadingComponent,
error: AppErrorComponent,
timeout: 3000
})
}
}
Here, the v-if
could be some boolean parameter dependent on the successful loading of assetToEdit
.
Upvotes: 1
Reputation: 11
for VUE 3 composition API , use defineasynccomponent and also a hook before update lifecycle
example passing the component as a prop
....
//need two components for substitute your desired component
import LoadingComponent from "~/components/LoadingComponent.vue"
import ErrorComponent from "~/components/ErrorComponent.vue"
...
// we get component name from the props
const props = defineProps<{
componentName: string
}>()
//define the const to mount our component as ref so we can change //his value
const comp = shallowRef()
comp.value = defineAsyncComponent({
// the loader function
loader: () => import(`../../components/${props.componentName}.vue`),
// A component to use while the async component is loading
loadingComponent: LoadingComponent,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// A component to use if the load fails
errorComponent: ErrorComponent,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000 ,
suspensible : false,
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
retry();
} else {
fail();
}
}
})
onBeforeUpdate(()=>{
comp.value = defineAsyncComponent(() => import(`../../components/${props.componentName}.vue`))
})
...
//then on <template> just use
<component :is="comp" ></component>
if you dont want messages on console complaining you can follow errorhandler
Upvotes: 1
Reputation: 1033
what worked for me is awaiting componentLoader function since the import function returns a promise!
async componentLoader () {
return await import('@/components/testDynamic')
}
Upvotes: 2
Reputation: 600
I use the following method a lot (Vue 2), it works well.
The following simple example allows a component that is to be loaded, to be specified dynamically: -because the call to launchEditForm just takes a string as a parameter. If you can formulate the filename as a string then you can load it.
It is important to keep launchEditForm as a "computed" property i.e. do not define it in "methods".
If the file that you call does not exist then you will get a runtime error along the lines of: Cannot find module './components/cliente/forms/DoesNotExist.vue'
<template>
<div>
<button
type="button"
@click="launchEditForm('components/clients/forms/EditContactsForm.vue')"
>
Edit Contacts
</button>
<component :is="currentEditForm" />
</div>
</template>
<script>
export default {
name: 'Screen42',
data() {
return {
componentToDisplay: null
};
},
computed: {
currentEditForm: function() {
if (this.componentToDisplay) {
return () => import(`@/${this.componentToDisplay}`);
}
return null;
}
},
methods: {
launchEditForm(fileName) {
this.componentToDisplay = fileName;
}
}
};
</script>
Note in the above example the file resides at .../src/components/clients/forms/EditContactsForm.vue
Often this type of dynamic import would be done in a modal, and its just a case of moving the <component> tag as is, inside of the modal. Obviously we can use v-if's with boolean variables to open and close the modals as desired. But the code above should illustrate the core point -i.e that the component name can be dynamicly generated and the component dynamically loaded.
Upvotes: 3
Reputation: 674
I was having the same issue and because I was using Vue 3, none of the solutions given here worked for me. After some research, I found out that the procedure to define dynamic components (async components) is a little different in Vue 3. I hope this code helps someone.
<template>
<component :is="comp"></component>
</template>
<script>
//Vue 3 is having a special function to define these async functions
import {defineAsyncComponent} from "vue";
export default {
name: "DynamicComponent",
//I am passing the name of the Component as a prop
props: {
componentName:{
type: String,
required: true
}
},
computed: {
comp () {
return defineAsyncComponent(() => import(`@/components/${this.componentName}.vue`))
}
}
}
</script>
Upvotes: 28
Reputation: 18197
You can register async dynamic components locally in a single file component like this:
export default {
components: {
'test-dynamic': () => import('@/components/testDynamic'),
'other-dynamic': () => import('@/components/otherDynamic')
},
data () {
return {
current: 'test-dynamic'
}
}
}
And in your template:
<component :is="current"></component>
If you register multiple components then you would just change the value of current
to the desired component.
In the case of many components, you can import an object mapping the component names to their respective file paths, then register them like:
import myComponents from '@/components'
export default {
components: Object.keys(myComponents).reduce((obj, name) => {
return Object.assign(obj, { [name]: () => import(myComponents[name]) })
}, {})
...
}
Where myComponents
is exported as:
// components/index.js
export default {
foo: '@/path/to/Foo',
bar: '@/path/to/Bar',
...
}
Upvotes: 36