How to use dynamic Components in Vue

Understanding dynamic and async components by example

Hi guys, in today’s post I want to share an interesting scenario about using dynamic components with you.

Imagine this situation where you have a search feature in your application. A pretty common feature to have, right? There’s a search field, maybe some more advanced filters to complement it, you grab the results from the backend and need to display what was found.

It’s reasonable to think of writing something like the code below:

<div
    v-for="(item, index) of items"
    :key="index"
>
    <!-- Results are displayed here... -->
</div>

Now, imagine that this search brings all kinds of results, where the backend endpoint returns multiple entities, and each entity is displayed differently on the page. Something like this:

{
    "items": [
        {
            "type": "client",
            "data": { "id": 1, "name": "John Doe", "phoneNumber": "+5512345678" }
        },
        {
            "type": "product",
            "data": { "id": 98, "description": "Product description", "imageURL": "https://someimage.png", "value": 10.99 }
        },
        {
            "type": "service",
            "data": { "id": 86, "description": "Service description", "notes": "Notes about this service", "value": 100.53 }
        }
    ]
}

Looking at the payload above we can see three different categories of information. To display the items on the page we have the following component:

<div
    v-for="(item, index) of items"
    :key="index"
>
    <div v-if="item.type == 'service'">
        <!-- service information goes here... -->
    </div>

    <div v-if="item.type == 'product'">
        <!-- product information goes here... -->
    </div>

    <div v-if="item.type == 'client'">
        <!-- client information goes here... -->
    </div>
</div>

Break it into small pieces

The code shown previously will work, but, we can (and should) break it down into smaller pieces.

Some lines of code were omitted from the last code snippet. Keep in mind that you’ll have more elements for each category. It’s too easy to be caught in a mess thinking like that… So, we can improve it by organizing each category into a specific component. By doing this our code will look more like this:

<!-- SearchResult.vue -->
<template>
    <div
        v-for="(item, index) of items"
        :key="index"
    >
        <DisplayService :data="item.data" v-if="item.type == 'service'" />
        <DisplayProduct :data="item.data" v-if="item.type == 'product'" />
        <DisplayClient :data="item.data" v-if="item.type == 'client'" />
    </div>
</template>

<script>
import DisplayService from 'DisplayService.vue'; 
import DisplayProduct from 'DisplayProduct.vue'; 
import DisplayClient from 'DisplayClient.vue'; 

export default {
    name: 'SearchResult',
    components: {DisplayService, DisplayProduct, DisplayClient},
    setup() {}
}
</script>

Each component has its logic and its template to display the information. They just receive the data prop. Let’s look a little bit at the structure of the DisplayClient.vue component.

<template>
    <!-- the template goes here -->
</template>

<script>
export default {
    name: 'DisplayClient',
    props: {
        data: {type: Object, required: true, default: null}
    },
    setup() {}
};
</script>

The above example shows the structure of the DisplayClient. This is structure is quite similar to the DisplayService and DisplayProduct.

Let’s look for improvement points

Our last SearchResult.vue snippet is a little better than the first one presented. Although it’s better organized it isn’t very different looking from the SerchResult.vue perspective.

We have main concerns to pay attention to in our current code:

  • we’re still using v-if to display each piece of information;
  • we’re importing components that might not be used.

The second one is more about dynamic loading. To clarify, suppose that the endpoint returns all the items as being services. It means that we only need to display the DisplayService component. However, we’re importing two other components, and in this possible scenario, they won’t be used. With dynamic loading, we can avoid this and import only the components we’re going to use.

Let’s make these improvements!

Implementing dynamic components

At first, let’s look at a more complete version of SearchResult.vue after implementing dynamic components.

<!-- SearchResult.vue -->
<template>
    <component
        v-for="(item, index) of items"
        :key="index"
        :is="mapTypeComponents[item.type]"
        v-bind="item"
    />
</template>

<script>
import DisplayService from 'DisplayService.vue'; 
import DisplayProduct from 'DisplayProduct.vue'; 
import DisplayClient from 'DisplayClient.vue';
import { ref } from 'vue';

export default {
    name: 'SearchResult',
    components: {DisplayService, DisplayProduct, DisplayClient},
    setup() {
        const items = ref([]);
        const mapTypeComponents = {
            product: DisplayProduct,
            service: DisplayService,
            client: DisplayClient
        };

        const doSearch = async () => {
            items.value = await getItems();
        };

        return {
            mapTypeComponents,
            doSearch,
            items
        };
    }
}
</script>

Let’s talk about each change made in our code.

  • The usage of component

    This component tag means that this is a dynamic component where we can define the component that will be loaded by assigning the is prop.

    To make it happen we have the object mapTypeComponents which maps the type to the corresponding component.

  • The v-for

    The v-for is now in the component not in a parent div since we don’t need to display the component based on a v-if.

  • Props passing

    The v-bind passes the item attribute as props to the component, thus each component can receive type and data as props. But we programmed our components to get just the data which is enough for what we need.

Our code looks better. It is more readable as well as more organized than the previous versions.

Loading components dynamically

Now that we have our dynamic components coded and ready to go, let’s save a few resources on our page by loading only what is needed to render the search results.

The concept behind this is the Async Components which are described in the VueJS documentation.

In large applications, we may need to divide the app into smaller chunks and only load a component from the server when it’s needed. To make that easier, Vue allows you to define your component as a factory function that asynchronously resolves your component definition.

To get it done we need to adjust our mapTypeComponents by importing components using the composable defineAsyncComponent.

As a reminder, this approach is going to work if you’re using Composition API and Vue 3. Check out the link in References section and get to know how to implement using Options API and/or Vue 2.

Using the defineAsyncComponent our code will look like this:

<template>
    <component
        v-for="(item, index) of items"
        :key="index"
        :is="mapTypeComponents[item.type]"
        v-bind="item"
    />
</template>

<script>
import { defineAsyncComponent, ref } from 'vue';

export default {
    name: 'SearchResult',
    setup() {
        const items = ref([]);
        const mapTypeComponents = {
            product: defineAsyncComponent(() => import('DisplayProduct.vue')),
            service: defineAsyncComponent(() => import('DisplayService.vue')),
            client: defineAsyncComponent(() => import('DisplayClient.vue'))
        };

        const doSearch = async () => {
            items.value = await getItems();
        };

        return {
            mapTypeComponents,
            doSearch,
            items
        };
    }
}
</script>

We removed the components importing right after the script tag, and we changed the object mapTypeComponents replacing the component itself with the defineAsyncComponent with a function returning the import inside of it.

Summary

In this article, we learned how to use some powerful VueJS concepts. Concepts that, in this case, made our solution more readable, elegant, and performative. All that is in a context that you might face within your journey as a VueJS developer.

Let me know if you have any questions and If you liked this content consider sharing it with your friends.

That’s all. See ya!

References

We are hiring new talents. Do you want to work with us? become@codeminer42.com