typescriptvue.js

Vue 3 dynamcially load async components


I want to display a component depending on it's type. Let me explain.

I have multiple components that are similar to each other but depending on the given type they should display something different. With the defineAsyncComponent method I can import the components and use them easiliy. Example:

const CheckboxControl = defineAsyncComponent(
  () => import('@/components/editor/controls/CheckboxControl.vue'),
);

This works fine, but if I do this I have a ton of imported components. I don't want this. My approach is to wrap the defineAsyncComponent inside an arrow function like this:

const loadComponent = async (type: string) =>
  defineAsyncComponent(
    () =>
      import(
        `@/components/editor/controls/${type[0].toUpperCase()}${type.substring(
          1,
        )}Control.vue`
      ),
  );

In the template I am then able to render the component like this <component :is="renderComponent(control.type)" />

But this gives me the following warning:

[Vue warn]: Component is missing template or render function.

Awaiting the defineAsyncComponent method doesn't solve the issue.

What am I doing wrong? How can I dynamically import these components?

Update

Here are all the possibilities that can be inside the control.type attribute:

Update 2

This is my current code that is working:

const CheckboxControl = defineAsyncComponent(
  () => import('@/components/editor/controls/CheckboxControl.vue'),
);

const DateControl = defineAsyncComponent(
  () => import('@/components/editor/controls/DateControl.vue'),
);

const EmailControl = defineAsyncComponent(
  () => import('@/components/editor/controls/EmailControl.vue'),
);

const NumberControl = defineAsyncComponent(
  () => import('@/components/editor/controls/NumberControl.vue'),
);

const RadioControl = defineAsyncComponent(
  () => import('@/components/editor/controls/RadioControl.vue'),
);

const RangeControl = defineAsyncComponent(
  () => import('@/components/editor/controls/RangeControl.vue'),
);

const SelectControl = defineAsyncComponent(
  () => import('@/components/editor/controls/SelectControl.vue'),
);

const TextareaControl = defineAsyncComponent(
  () => import('@/components/editor/controls/TextareaControl.vue'),
);

const TextControl = defineAsyncComponent(
  () => import('@/components/editor/controls/TextControl.vue'),
);

const loadComponent = (type: string) => {
  switch (type) {
    case 'checkbox':
      return CheckboxControl;
    case 'date':
      return DateControl;
    case 'email':
      return EmailControl;
    case 'number':
      return NumberControl;
    case 'radio':
      return RadioControl;
    case 'range':
      return RangeControl;
    case 'select':
      return SelectControl;
    case 'textarea':
      return TextareaControl;
    case 'text':
      return TextControl;
    default:
      // TODO: show error component if type not supported
      break;
  }
};

Update 3

For my current setup I use vite as the build tool. The vite version I use is 2.9.5. The vue version I use is 3.2.33 and the typescript version 4.6.3.


Solution

  • Thanks @Estus Flask for your help :)

    So the problem was that I was trying to import it with the @ alias. I changed my method to this:

    const loadComponent = (type: string) =>
      defineAsyncComponent(
        () =>
          import(
            `./controls/${type[0].toUpperCase()}${type.substring(1)}Control.vue`
          ),
      );
    

    This works now.

    I don't know why in this case it is not working with the @ alias, because it works when I use it in

    const CheckboxControl = defineAsyncComponent(
      () => import('@/components/editor/controls/CheckboxControl.vue'),
    );
    

    Maybe someone has an explanation to this?