Search code examples
vue.jsvuejs2jsxvue-dynamic-components

How to create a dynamic vue component that has a computed template containing another component with Object properties without passing it as string


I have a component like this:

Relation.vue

<template>
    <div :is="dynamicRelation"></div>
</template>

<script>
  import Entry from '@/components/Entry';
  import weirdService from '@/services/weird.service';
  export default { 
      name: 'Relation',
      data() {
         return {
             entry1: { type: 'entity', value: 'foo', entity: {id: 4}},
             entry2: { type: 'entity', value: 'bar', entity: {id: 5}},
             innerText: '@1 wut @2',
         } 
      },
      computed: {
          dynamicRelation() {
              return {
                  template: `<div>${this.innerText
                        .replace('@1', weirdService.entryToHtml(this.entry1))
                        .replace('@2', weirdService.entryToHtml(this.entry2))}</div>`,
                  name: 'DynamicRelation',
                  components: { Entry }
              };
          }
      }
  }
</script>

wierd.service.js

export default {
   entryToHtml(entry) {
       [some logic]
       return `<entry entry='${JSON.stringify(entry)}'></entry>`;
       // unfortunately I cannot return JSX here: <entry entry={entry}></entry>; 
       // I get 'TypeError: h is not a function'
       // unless there is a way to convert JSX to a pure html string on the fly
    }
}

Entry.vue

<template>
    <div>{{objEntry.name}}</div>
</template>

<script>
  export default { 
      name: 'Entry',
      props: {
          entry: String // I need this to be Object
      },
      computed: {
          objEntry() {
              return JSON.parse(this.entry);
          }
       }
   }
</script>

The innerText property decides how the components will be rendered and it can be changing all the time by having its @ slots in any position. In this example the result is:

<div>
   <div>foo</div> 
   wut 
   <div>bar</div>
</div>

This works since Entry component has as a property entry that is of type String but I have to JSON.stringify() the entry object in weirdService side and then in Entry component I have to JSON.parse() the string to get the real object back. How can I make the above work so that I pass an object directly to a dynamic template so I avoid serialization and deserialization all the time.

btw for this to work runtimeCompiler needs to be enabled in vue.config.js:

module.exports = { 
    runtimeCompiler: true
}

I know I can use JSX to return components with objects in them but this is allowed only in render() function it seems and not custom ones like mine.


Solution

  • I was able to do what I wanted by using JSON.stringify still but pass the entry as object :entry

    wierd.service.js

    export default {
       entryToHtml(entry) {
           return `<entry :entry='${JSON.stringify(entry)}'></entry>`;
        }
    }
    

    Entry.vue

    <template>
        <div>{{entry.name}}</div>
    </template>
    
    <script>
      export default { 
          name: 'Entry',
          props: {
              entry: Object
          }
       }
    </script>