John
John

Reputation: 1297

How to render components dynamically in Vue JS?

I am making a form generator, which uses components in it for input fields, buttons etc. I want to be able to generate the form depending on what options I pass to it.

But I can't get it to render the components.

I tried to return pure HTML but that won't render the components.

I call the form generator from my Home.vue template where I want the form with an options object like this:

options: {
    name: {
        type: 'input',
        label: 'Name'
    },
    submit: {
        type: 'button',
        label: 'Send'
    }
}

In template:

<template>
  <form-generator :options="options"></form-generator>
</template>

In the form generator component I have tried multiple things like:

<template>
  {{ generateForm(this.options) }}
  // ... or ...
  <div v-html="generateForm(this.options)"></div>
</template>

I include all the components like:

import {
  FormButton,
  FormInput
} from './FormComponents'

Now the final part is how do I make FormInput render?

This does not work since it outputs the HTML literally:

methods: {
  generateForm(options) {

    // .. do stuff with options ..
    var form = '<form-input />'

    return form
  }
}

Upvotes: 18

Views: 35908

Answers (3)

Tomer
Tomer

Reputation: 17940

Vue has a very simple way of generating dynamic components:

<component :is="dynamicComponentName"></component>

So I suggest you define the options as an array and set the type to be the component name:

options: [
   {
        type: 'FormInput',
        propsData: {label: 'Name'}
    },
    {
        type: 'FormButton',
        propsData: {label: 'Send'}
    }
]

Then use it in the form generator like this:

<component :is="option.type" v-for="option in options"></component>

You can also pass properties as you'd pass to any other component, but since it's dynamic and every component has a different set of properties i would pass it as an object and each component would access the data it needs:

<component :is="option.type" v-for="option in options" :data="option.propsData"></component>

UPDATE

Since you don't have control of the components it requires a bit more manipulation:

For each component that requires text, add a text attribute in the options:

options: [
       {
            type: 'FormInput',
            propsData: {label: 'Name'}
        },
        {
            type: 'FormButton',
            text: 'Send',
            propsData: {label: 'Send'}
        }
    ]

And then just use it in the component:

<component :is="option.type" v-for="option in options">{{option.text}}</component>

For passing attributes, I think you can pass it using v-bind and then it will automatically destructure them, so if a button accepts 2 props: rounded, color the options would look like:

{
  type: 'FormButton',
  text: 'Button',
  propsData: {rounded: true, color: '#bada55'}
}

and then the component:

<component :is="option.type" v-for="option in options" v-bind="option.propsData">{{option.text}}</component>

Upvotes: 42

Dylan Pierce
Dylan Pierce

Reputation: 4668

@tomer's answer is really good. And I think it's correct.

Just in my case, we had a special Vite configuration that required me to pass the actual component instance rather than by a string of the name of the component:

import FormInput from '@components/form/FormInput'
import FormButton from '@components/form/FormButton'

options: [
   {
        type: FormInput,
        propsData: {label: 'Name'}
    },
    {
        type: FormButton,
        propsData: {label: 'Send'}
    }
]


<component :is="option.type" v-for="option in options"></component>

Upvotes: 1

M. Emre Yal&#231;ın
M. Emre Yal&#231;ın

Reputation: 654

you can create an Array like this:

components_data: [
            {
                name: 'checkbox',
                data: false
            },
            {
                name: 'text',
                data: 'Hello world!'
            }
        ]

and then loop through this array inside of the <component>:

<component
        v-for="(component,i) in components_data"
        :key="i"
        :is="component.name"
        :data="component.data"
    />

this will create 2 component [<text>, <checkbox>] dynamically and give them data via props.

when you push new data like this components_data.push({name:'image',data: {url:'cat.jpg'}}) it will render a new component as <image :data="{url:'cat.jpg'}"/>

Upvotes: 3

Related Questions