35

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?

7
  • maybe try return () => require('@/components/testDynamic') Commented Nov 21, 2018 at 6:28
  • or, register them globally Commented Nov 21, 2018 at 6:32
  • require doesn't throw any error, but it is not rendering any thing. Commented Nov 21, 2018 at 6:34
  • 2
    @Andrew1325 Global registration means, it will load all the components in the global level right, all components loaded at once and can use any where. But they all load at once right initially? I want it be import on demand only. Commented Nov 21, 2018 at 6:57
  • Are you compiling your templates? cause maybe it's looking to import on the wrong place, i would suggest to try and load all 50 components, and see how big it really is, see if you even need a solution like the one you think you need... If you really need it, i think you would have to generate/compile each template individually, and then point the import to wherever you put them... Commented Nov 21, 2018 at 7:01

6 Answers 6

36

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',
  ...
}
Sign up to request clarification or add additional context in comments.

11 Comments

In this case, what ever component we mentioned in the components section, will be loaded initially right? i have almost 50 components, Importing all those components/files are heavy right? That why i am trying to import component on demand.
Nope, they are loaded only when needed.
Hey i am trying this 'test-dynamic': () => import('@/components/testDynamic'), that what you mentioned in the component section, but still i am getting same error, which i have mentioned in the issue.
Are you using vue cli and/or babel? Sounds like it isnt transpiling the code correctly?
Not sure, i created project using vue init webpack . and i am trying to build, it shows build is complete without errors, but these errors are coming in browser console. I can see babel, babel-loader and few other with babel in my package.json.
|
28

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>

3 Comments

How to get data from child components $emit dynamically?
How to catch errors if component doesn't exist?
@AlpeshPatil: calling an error component if your import fails is well described in Vue doc. However it will still throw an error in the console even if you don't want to. To avoid it, a start of answer here.
3

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.

Comments

2

what worked for me is awaiting componentLoader function since the import function returns a promise!

async componentLoader () {
  return await import('@/components/testDynamic') 
}

Comments

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

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.