vue.jsvuejs2vue-component

Import components dynamically from a list in Vue 2


I have a portal which provides different services in form of a vue component. We are saving all settings for a service in a database (including the path (=pfad) where it is stored). With this data we provide a list of all active services which should be loaded (there are 50+ services so we don't want to write a new import each time a new service is added).

It also should catch errors if one service-file doesn't exist (we are developing this tool with multiple person, so one would declare a new service in the database, but has it only on his feature-branch).

I tried the following solution in the ServiceMain.vue, but the imported services didn't get mounted.

HTML part:

<template>
    <b-row>
        <b-col>
            <!--Visible Services need the Service-Box so the user can click it. Import of the Service happens in teh Service-Box-->
            <div>
                <service-box v-for="service in filteredServices.slice((rowIdx - 1) * 3, rowIdx * 3)" :key="service.id"
                    :service="service" />
            </div>
            <!--Invisible Services should only get a component tag, they should not be visible to the user-->
            <div
                v-if="invisibleServices.length > 0"
                style="!important display: none;">
                <component
                    :is="componentFile"
                    v-for="(componentFile, index) in componentFilesOfInvisibleServices"
                    :key="'invisibleComponent_' + index"
                />
            </div>
        </b-col>
    </b-row>
</template>

Script part:

export default {
    name: "ServicesMain",
    // components: {...},
    data: function () {
        return {
            services: [],
            visibleServices: [],
            invisibleServices: [],
        };
    },
    computed: {
        componentFilesOfInvisibleServices() {
            let files = [];
            for (const invisibleService of this.invisibleServices) {
                let componentName = invisibleService.pfad;
                try {
                    let componentFile = import(`@/components/dialogs/${componentName}.vue`);
                    files.push(componentFile);
                } catch (e) {
                    // later handel the Error if a file is not found
                    console.error(e)
                }
            }
            return () => files;
        },
    },
    mounted() {
        this.getServices();
    },
    methods: {
        getServices() {
            ticketApi
                .getTicketTypes()
                .then(response => {
                    this.services = response.data;
                    this.visibleServices = response.data.filter(service => service.isVisible);
                    this.invisibleServices = response.data.filter(service => !service.isVisible && service.parentTicket === undefined);
                    // ...
                })
                .catch(error => {
                    console.error(error);
                });
        }
    }
};
</script>

If I use instead a function which loads the Module in the :is="..." I ran into an endless Loop

I also searched at stackoverflow and found the following threads, none of them worked or had solutions for me.

Vue- Import vue components dynamically

Use dynamic import in Vue functional component

Dynamic imported vue component failed to resolve

This solution would theoretical work but its an import of all with out filtering, we need the option to filter te components in the vue-script part.

Auto Import Components Inside A Component In Vue JS


Solution

  • There where multiple problems with the code, the following soluiton worked out for me: The HTML part dont need to be edited. The computed property "componentFilesOfInvisibleServices" can be deleted, instead we need "componentFilesOfInvisibleServices" as an empty Array in data. The following method shuold be called in "getServices" in the then-block after the other code.

    async loadComponentFilesOfInvisibleServices() {
    
      this.componentFilesOfInvisibleServices = await Promise.all(
          this.invisibleServices.map(async (service) => {
            let componentName = service.pfad.substring(service.pfad.lastIndexOf('$') + 1);
    
            const module =  await import(`@/components/dialogs/${componentName}.vue`);
            this.$options.components[componentName] = module.default;
    
            return module;
          })
      );
    },

    Also i neted to add ".default" to module befor return, so the default of the componet is returned.

    As explenation, before using ".default" i got an error saying:

    Failed to mount component: template or render function not defined

    After inspecting "module" I figured you have to use ".default" in addition.