I have a giant, dynamic HTML string that I'm loading into a div
within a Vue component. The HTML string is essentially the content from a WYSIWYG editor. Originally, I was just using v-html
for this, and it was fine.
However, there are now cases where I need to replace part of the HTML string with an actual Vue component, and I'm not sure of the best way to do that.
As an example, I might have some markup in the HTML string that looks like the following:
||map:23||
And what I want to do is replace that with a Vue component like the following:
<map-component :id="23"></map-component>
I tried doing the string conversion ahead of time in Laravel and then just using v-html
in the Vue component to inject the content, but that doesn't seem to load the Vue component.
I then tried using a slot for the HTML content, and that does work, but it has the nasty side effect of showing a bunch of unformatted HTML content on the screen for a second or two before Vue is able to properly render it.
So my question is: Is there another (more elegant) way to do this? I was thinking that after the Vue component loads with the HTML content, I could somehow find the, for example, ||map:23||
instances in the markup and then dynamically replace them with the correct Vue component, but if that's possible, I don't know how; I couldn't find anything in the Vue docs.
Does anyone know if this is possible? Thank you.
You can use Vue.compile
to compile a template string (that can include vue components).
Then you can combine this with a component that has a render()
method, to just render the template:
// this component will dynamically compile the html
// and use it as template for this component.
Vue.component("dynamic-html", {
props: ["html"],
computed: {
template() {
if(this.html)
return Vue.compile(this.html).render;
return null;
}
},
render() {
if(this.template)
return this.template();
return null;
}
});
This allows you to render arbirary template strings, which can also contain vue components:
<dynamic-html html="<some-component></some-component>">
</dynamic-html>
Additionally, you can also use this to pass down props / event handlers to components within your string:
<!-- Passing down props -->
<dynamic-html
html='<some-component :prop="$attrs.myprop"></some-component>'
:myprop="12"
></dynamic-html>
<!-- passing down events -->
<dynamic-html
html='<some-component @click="$emit('foo', $event)"></some-component>'
@foo="doSomething"
></dynamic-html>
(you need to use $attrs
though to access the props, because they're not in the props
definition of the dynamic-html
component)
Full code example:
// this component will dynamically compile the html
// into a vue component
Vue.component("dynamic-html", {
props: ["html"],
computed: {
template() {
if(this.html)
return Vue.compile(this.html).render;
return null;
}
},
render() {
if(this.template)
return this.template();
return null;
}
});
Vue.component("red-bold-text", {
props: ["text"],
template: '<span class="red">{{text}}</span>'
});
new Vue({
el: '#root',
data: {
html: null,
myBoundVar: "this is bound from the parent component"
},
mounted() {
// get the html from somewhere...
setTimeout(() => {
this.html = `
<div>
WELCOME!
<red-bold-text text="awesome text"></red-bold-text>
<red-bold-text :text="$attrs.bound"></red-bold-text>
<button @click="$emit('buttonclick', $event)">CLICK ME</button>
</div>
`;
}, 1000);
},
methods: {
onClick(ev) {
console.log("You clicked me!");
}
}
});
.red { color: red; font-weight: bold; margin: 6px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root">
<div>This will load dynamically:</div>
<dynamic-html :html="html" :bound="myBoundVar" @buttonclick="onClick"></dynamic-html>
</div>
Turtlefight's answer is very helpful and complete, but for anyone looking for a quick, simple answer, use the literal component
component with :is
as follows to inject HTML content containing Vue components into a dynamic component:
// htmlStrWithVueComponents is a large string containing HTML and Vue components.
<component
:is="{
template: `<div>${htmlStrWithVueComponents}</div>`
}"
>
</component>
Here're a couple of sources that describe this technique:
Edit: It's worth noting that component :is
is fairly limited in what you can do. You can't make very complex template
strings or added mounted
methods, etc.
For my particular use case, because I needed some of this more complex stuff, I ended up going with the following, which is kind of a hybrid between the simpler answer above and Turtlefight's answer:
// This code goes within the parent component's mounted method or wherever is makes sense:
Vue.component('component-name-here', {
// Can add much more complex template strings here.
template: `
<div class="someClass">
${content}
</div>
`,
// Can add lifecycle hooks, methods, computed properties, etc.
mounted: () => {
// Code here
}
});
const res = Vue.compile(`
<component-name-here>
</component-name-here>
`);
new Vue({
render: res.render,
staticRenderFns: res.staticRenderFns
}).$mount('dom-selector-for-dom-element-to-be-replaced-by-vue-component');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With