I'm attempting to add ponents programatically to a page in my Vue Single file Component which works beautifully if you know what you'd like to do ahead of time. In my case, the ponents and their layout will be received via JSON and I'm attempting to create them dynamically. Below you can see I'd like to do something like eval but this doesn't work for uninstantiated objects unfortunately. The code below is simplified with hardcoded values to get the point across.
<template>
<div id="app">
<h2>Static template:</h2>
<InputTemplate type="text"></InputTemplate>
<h2>Dynamically inserted:</h2>
<div ref="container">
<button @click="onClick">Click to insert</button>
<br/>
</div>
</div>
</template>
<script>
import Vue from 'vue'
//the ponent I'd like to create
import InputTemplate from './ponents/InputTemplate.vue'
export default {
name: 'app',
ponents: { InputTemplate },
methods: {
onClick() {
//I'd like to do something like this, but eval doesn't work like this
var ComponentClass = Vue.extend(eval('InputTemplate'))
var instance = new ComponentClass({
propsData: {type: 'text' }
})
instance.$mount()
this.$refs.container.appendChild(instance.$el)
}
}
}
</script>
I put together a helper function below that replaces eval and works, but feels kinda inelegant for the solution. It also requires a lot of upkeep for each new ponent added. Is there a better way to do this?
returnComponent(ponentString){
if(ponentString === 'InputTemplate'){
return InputTemplate;
}
}
I'm attempting to add ponents programatically to a page in my Vue Single file Component which works beautifully if you know what you'd like to do ahead of time. In my case, the ponents and their layout will be received via JSON and I'm attempting to create them dynamically. Below you can see I'd like to do something like eval but this doesn't work for uninstantiated objects unfortunately. The code below is simplified with hardcoded values to get the point across.
<template>
<div id="app">
<h2>Static template:</h2>
<InputTemplate type="text"></InputTemplate>
<h2>Dynamically inserted:</h2>
<div ref="container">
<button @click="onClick">Click to insert</button>
<br/>
</div>
</div>
</template>
<script>
import Vue from 'vue'
//the ponent I'd like to create
import InputTemplate from './ponents/InputTemplate.vue'
export default {
name: 'app',
ponents: { InputTemplate },
methods: {
onClick() {
//I'd like to do something like this, but eval doesn't work like this
var ComponentClass = Vue.extend(eval('InputTemplate'))
var instance = new ComponentClass({
propsData: {type: 'text' }
})
instance.$mount()
this.$refs.container.appendChild(instance.$el)
}
}
}
</script>
I put together a helper function below that replaces eval and works, but feels kinda inelegant for the solution. It also requires a lot of upkeep for each new ponent added. Is there a better way to do this?
returnComponent(ponentString){
if(ponentString === 'InputTemplate'){
return InputTemplate;
}
}
Share
Improve this question
edited Feb 18, 2018 at 21:36
Matthew Hiles
asked Feb 18, 2018 at 21:11
Matthew HilesMatthew Hiles
1933 silver badges9 bronze badges
3
- 1 Have you tried Async ponents? – thibautg Commented Feb 18, 2018 at 22:19
-
If I'm understanding correctly, using Async ponents would allow me to dynamically import the ponents as necessary by calling them with something like
myComponent: () => import(myComponent)
? – Matthew Hiles Commented Feb 18, 2018 at 22:48 -
I haven't tried myself but this also how I understand it. I think you can replace the
import
function with another function returning a ponent object inside a Promise. – thibautg Commented Feb 18, 2018 at 23:02
2 Answers
Reset to default 4It turns out that Vue has <ponent :is="yourComponentType">
which does exactly what I was hoping to acplish if you throw it into a v-for
loop. Documents here.
You can use Async ponents to retrieve a ponent via an API.
I've tested with GraphQL via Axios but it should work with any REST service.
The main ponent looks like that:
<template>
<div>
<h1>Test page</h1>
<div><button @click="clicked = true">Load ponent</button></div>
<async-ponent v-if="clicked" />
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
clicked: false,
};
},
ponents: {
'async-ponent': () => axios.post('http://localhost:5000/graphql', { query: `
{
asyncComponentByRowId(rowId: 1) {
ponent
content
}
}
`,
}).then((response) => {
const p = response.data.data.asyncComponentByRowId.content;
// eval can be harmful
return eval(`(${p})`);
}),
},
};
</script>
The retrieved ponent is in the following format:
{
"data": {
"asyncComponentByRowId": {
"ponent": "my-async-ponent",
"content": "{\r\n\ttemplate: '<div>{{ msg }}</div>',\r\n\tdata: function() {\r\n\t\treturn {\r\n\t\t\tmsg: 'Async ponent works!'\r\n\t\t};\r\n\t}\r\n}"
}
}
}
The decoded data.asyncComponentByRowId.content
property is a string representing a JavaScript object:
{
template: '<div>{{ msg }}</div>',
data: function() {
return {
msg: 'Async ponent works!'
};
}
}
When the button is pressed, the ponent is shown after being downloaded from the API asynchronously.
The only problem is that I'm using eval
to decode the object.
Result: