Dynamically insert child components inside vuejs2 data (without $compile or abusing v-html)

As mentioned in the my comment above, $compile was removed, but Vue.compile is available in certain builds. Using that below works as I believe you intend except in a couple cases.

Vue.component('child', {
  // pretend I do something useful
  template: '<span>--&gt;<slot></slot>&lt;--</span>'
})

Vue.component('parent', {
  data() {
    return {
      input: 'lorem',
      text: '<div><p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p></div>'
    }
  },
  template: `<div>
      Search: <input type='text' v-model="input"><br>
      <hr>
      <div><component :is="output"></component></div>
    </div>`,
  computed: {
    output() {
      if (!this.input)
         return Vue.compile(this.text)
      /* This is the wrong approach; what do I replace it with? */
      var out = this.text;
      if (this.input) {
        this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
        var regex = new RegExp(this.input, "gi");
        out = out.replace(regex, '<child><b>' + this.input + '</b></child>');
        out = Vue.compile(out)
      }
      return out;
    }
  }
});

new Vue({
  el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
  <parent></parent>
</div>

You mentioned you are building with webpack and I believe the default for that build is Vue without the compiler, so you would need to modify it to use a different build.

I added a dynamic component to accept the results of the compiled output.

The sample text is not a valid template because it has more than one root. I added a wrapping div to make it a valid template.

One note: this will fail if the search term matches all or part of any of the HTML tags in the text. For example, if you enter "i", or "di" or "p" the results will not be what you expect and certain combinations will throw an error on compilation.


I'm posting this as a supplement to Bert Evans's answer, for the benefit of vue-cli webpack users who want to use .vue files instead of Vue.component(). (Which is to say, I'm mostly posting this so I'll be able to find this information when I inevitably forget it...)

Getting the right Vue build

In vue-cli 2 (and possibly 1?), to ensure Vue.compile will be available in the distribution build, confirm webpack.base.conf.js contains this line:

'vue$': 'vue/dist/vue.esm.js' // or vue/dist/vue.common.js for webpack1

instead of 'vue/dist/vue.runtime.esm.js'. (If you accepted the defaults when running vue init webpack you will already have the full standalone build. The "webpack-simple" template also sets the full standalone build.)

Vue-cli 3 works somewhat differently, and does not have Vue.compile available by default; here you'll need to add the runtimeCompiler rule to vue.config.js:

module.exports = {
    /* (other config here) */
    runtimeCompiler: true
};

The component

The "child" component can be a normal .vue file, nothing special about that.

A bare-bones version of the "parent" component would be:

<template>
    <component :is="output"></component>
</template>
<script>
import Vue from 'vue';
import Child from './Child'; // normal .vue component import

export default {
  name: 'Parent',
  computed: {
    output() {
      var input = "<span>Arbitrary single-root HTML string that depends on <child></child>.  This can come from anywhere; don't use unsanitized user input though...</span>";
      var ret = Vue.compile(input);
      ret.components = { Child };  // add any other necessary properties similarly
      ret.methods = { /* ... */ }  // like so
      return ret;
    }
  }
};
</script>

(The only significant difference between this and the non-webpack version is importing the child, then declaring the component dependencies as ret.components: {Child} before returning it.)

Tags:

Vuejs2