Sam
Sam

Reputation: 2425

Vue- Import vue components dynamically

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

Answers (6)

Wayne Smallman
Wayne Smallman

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

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

Muluken Getachew
Muluken Getachew

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

KenBuckley
KenBuckley

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

Rakshit Arora
Rakshit Arora

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

Brian Lee
Brian Lee

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

Related Questions