Search code examples
vue-componentvuejs3

Can I load the component template dynamically


Is it possible to load the template from server (include components) dynamically? Or can I change the template before it rendered?

I would like to let user to store their own form template into a database and generate the form according to the template-id.

I tried to change the this.$options.template, but it seems like only work for vue2.

<!-- static/myproj/js/my-field.vue -->
<template>
<label :for="name+'Fld'" v-html="title"></label>
<input :name="name" :type="text" :value="value" :id="name+'Fld'"/>
</template>

<script>
export default{
  props: {
    name: {type:String, required:true},
    value: {type:String, required:false, default:''},
    type:  {type:String, required:true},
    title: {type:String, required:false, default:'Field: '},
  },
  data: function(){ return {}; },
}
</script>
// index.vue
const loadVueModuleOpts= {
   moduleCache: {vue: Vue},  
   async getFile(url) {
     const res = await fetch(url);
     if ( !res.ok )
       throw Object.assign(new Error(res.statusText + ' ' + url), { res }); 
     return {
       getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
     }   
   },
};

export default{
   props: {
      id:            {required:true, type:String, default:'abcdefg'},
   }, 

   data: function(){
      this.loadSource();
      return {
         source:     null,
         target:     null,
      };
   },

   template: '<div>I\'m here to be replaced.</div>',

   created: async function(){
      this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
   },

   components: {
      'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
}
<!-- server response for /api/template/abcdefg -->
<form action="POST">
   <my-field name="name" title="Your Name: " type="text"/>
   <my-field name="email" title="Email: " type="email"/>
   <input type="submit"/><input type="reset"/>
</form>

Thanks.


Solution

  • Finally, I got the solution. According to Vue3: How to use Vue.compile in Vue3, we can render the template directly by Vue3 like this:

    // index.vue
    import { h, compile } from 'vue';
    
    const loadVueModuleOpts= {
       moduleCache: {vue: Vue},  
       async getFile(url) {
         const res = await fetch(url);
         if ( !res.ok )
           throw Object.assign(new Error(res.statusText + ' ' + url), { res }); 
         return {
           getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
         }   
       },
    };
    
    export default{
       props: {
          id:            {required:true, type:String, default:'abcdefg'},
       }, 
    
       data: function(){
          this.loadSource();
          return {
             source:     null,
             target:     null,
          };
       },
    
       // Magic here
       render: function(){
          if(this.target)
             return h(compile(this.target).bind(this));
          return h('div', 'Loading...');
       },
    
       created: async function(){
          this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
       },
    
       components: {
          'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
       },
    }