Dynamic html elements in Vue.js
Update: Based on this answer, you can do a similar dynamic-template component in Vue 2. You can actually set up the component spec in the computed
section and bind it using :is
var v = new Vue({ el: '#vue', data: { message: 'hi #linky' }, computed: { dynamicComponent: function() { return { template: `<div>${this.hashTags(this.message)}</div>`, methods: { someAction() { console.log("Action!"); } } } } }, methods: { hashTags: function(value) { // Replace hash tags with links return value.replace(/#(\S*)/g, '<a v-on:click="someAction">#$1</a>') } }});setTimeout(() => { v.message = 'another #thing';}, 2000);
<script src="//unpkg.com/vue@latest/dist/vue.js"></script><div id="vue"> <component :is="dynamicComponent" /></div>
Vue bindings don't happen on interpolated HTML. You need something Vue sees as a template, like a partial. However, Vue only applies bindings to a partial once; you can't go back and change the template text and have it re-bind. So each time the template text changes, you have to create a new partial.
There is a <partial>
tag/element you can put in your HTML, and it accepts a variable name, so the procedure is:
- the template HTML changes
- register new partial name for the new template HTML
- update name variable so the new partial is rendered
It's a little bit horrible to register something new every time there's a change, so it would be preferable to use a component with a more structured template if possible, but if you really need completely dynamic HTML with bindings, it works.
The example below starts out with one message, link-ified as per your filter, and after two seconds, changes message
.
You can just use message
as the name of the partial for registering, but you need a computed that returns that name after doing the registering, otherwise it would try to render before the name was registered.
var v = new Vue({ el: 'body', data: { message: 'hi #linky' }, computed: { partialName: function() { Vue.partial(this.message, this.hashTags(this.message)); return this.message; } }, methods: { someAction: function() { console.log('Action!'); }, hashTags: function(value) { // Replace hash tags with links return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>') } }});setTimeout(() => { v.$set('message', 'another #thing');}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script><partial :name="partialName"></partial>
I just learned about $compile
, and it seems to fit your need very nicely. A very simple directive using $compile
avoids all the registrations.
Vue.directive('dynamic', function(newValue) { this.el.innerHTML = newValue; this.vm.$compile(this.el);});var v = new Vue({ el: 'body', data: { message: 'hi #linky' }, computed: { messageAsHtml: function() { return this.message.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>'); } }, methods: { someAction: function() { console.log('Action!'); } }});setTimeout(() => { v.$set('message', 'another #thing');}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script><div v-dynamic="messageAsHtml"></div>
In Vue.js 2 it's easier:
new Vue({ ..., computed: { inner_html() { return ...; // any raw html }, }, template: `<div v-html='inner_html'></div>`,});