'How to add Vue 3 components programmatically?
Vue 3 doesn't have Vue.extend() method, so example here doesn't work: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
I have tried this solution: https://jsfiddle.net/jamesbrndwgn/fsoe7cuy/
But it causes warning in console:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw
or using shallowRef
instead of ref
.
So, what is the proper way to add components dynamically (programmatically) in Vue 3?
UploadTaskTagManager.vue
<template>
<button @click="addTag()" type="button">Add new tag</button>
<br>
<div>
<div v-for="child in children" :key="child.name">
<component :is="child"></component>
</div>
</div>
</template>
<script>
import UploadTaskTag from "./UploadTaskTag";
export default {
name: "UploadTaskTagManager",
components: {UploadTaskTag},
data() {
return {
children: []
}
},
methods: {
addTag() {
this.children.push(UploadTaskTag);
},
}
}
</script>
UploadTaskTag.vue
<template>
<div>
<select @change="onChangeTag($event)" v-model="currentTag">
<option v-for="tag in getAllTags()" :key="tag.tag_name" :value="tag">{{tag.tag_name}}</option>
</select>
<input maxlength="16" class="tag-input" ref="inputField"/>
<button @click="removeTag($event)" type="button">-</button>
<br>
</div>
</template>
<script>
export default {
name: "UploadTaskTag",
data() {
return {
tags: [],
currentTag: {}
}
},
methods: {
onChangeTag(event) {
this.$refs.inputField.value = this.currentTag.tag_name;
},
getAllTags() {
return this.$store.state.allTags;
},
removeTag() {
this.$el.parentElement.removeChild(this.$el)
},
getValue(fieldIndex) {
let tag = this.tags[fieldIndex];
return tag ? tag.tag_name : "";
},
}
}
</script>
Solution 1:[1]
just use createApp
instead of Vue.extend
here's a example of simple vue3 components programmatically
import Button from 'Button.vue'
import { createApp } from "vue"
// Creating the Instance
// use createApp https://v3.vuejs.org/api/global-api.html#createapp
var ComponentApp = createApp(Button)
import Button from "./Button.vue"
// inserting to dom
const wrapper = document.createElement("div")
ComponentApp.mount(wrapper)
document.body.appendChild(wrapper)
set props or slot is a little different by use h
, https://v3.vuejs.org/api/global-api.html#h
import Button from 'Button.vue'
import { createApp, h } from "vue"
var ComponentApp = createApp({
setup () {
return () => h(Button, {type: "primary"}, "default slot as children")
}
})
Solution 2:[2]
If I understood you correctly, then the solution will be like this
You can learn more about 'defineAsyncComponent' on the official website https://v3.vuejs.org/api/global-api.html#defineasynccomponent
<template>
<button @click="addTag()" type="button">Add new tag</button>
<br>
<div>
<div v-for="child in children" :key="child.name">
<component :is="child"></component>
</div>
</div>
</template>
<script>
import { defineAsyncComponent, defineComponent, ref } from "vue"
export default defineComponent({
components: {
UploadTaskTag: defineAsyncComponent(() => import("./UploadTaskTag"))
},
setup() {
const children = ref([])
const addTag = () => {
children.value.push('UploadTaskTag')
console.log(children.value)
}
return {
addTag,
children
}
},
})
</script>
Solution 3:[3]
I needed to do such a thing to add a Component
to a contenteditable
div.
To do so, I used the Teleport
component, bound to and array containing the name of the wished componend, the div where it'll be mount and optionaly the props.
Here is a generic code sample.
Image = {
template: document.getElementById('image-template'),
props: ['src']
}
const app = Vue.createApp({
template: document.getElementById('app-template'),
components: {
Image
},
data() {
return {
dynamicComponents: []
}
},
methods: {
async addImg() {
let span = document.createElement("span");
document.getElementById('main').appendChild(span);
await this.$nextTick();
this.$appendComponent(
'Image',
span, {
src: 'https://images-na.ssl-images-amazon.com/images/I/81-jLAuCQzL.png'
}, {
click: () => {
alert('Clicked')
}
});
}
}
});
app.config.globalProperties.$appendComponent = function(name, target, props, listeners = {}) {
this.$root.dynamicComponents.push({
name,
target,
props,
listeners
});
}
app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id="app">
</div>
<template id="app-template">
<Teleport v-for="{name, target, props, listeners},i in dynamicComponents" :key="i" :to="target">
<Component :is="name" v-bind="props" v-model="props.modelValue" v-on="listeners"/>
</Teleport>
<button @click="addImg">Add Image</button>
<div id="main">
Coucou
</div>
</template>
<template id="image-template">
<img :src="src" />
</template>
You can now programatically create DOM nodes anywhere, then adding an entry to the $root.dynamicComponents
array will mount a component in it.
Context consistant and no bloating.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | ethan_you |
Solution 2 | |
Solution 3 |