vue-custom-element: slots not correctly injected in Chrome and Opera

I created a Vue component “simple-form” for building dynamic forms. It’s using vue-custom-element in order to be able to inject it in my websites. To be able to specify a custom header, footer and other stuff for each form, I’m defining several slots in the components template.

Basically, the template looks something like this:

<div>
	<slot name="header"></slot>
	<h1>title</h1>
	<slot name="intro"></slot>
	<form>
		<div id="startmsg">
		<slot name="startmsg"></slot>
		</div>

		<!-- Dynamic form content goes here -->

		<slot name="endmsg"></slot>
	</form>
	<slot name="footer"></slot>
</div>

Everything works fine, except that in Chrome and Opera the slots are not correctly injected.

This is how I use the component in my website:

<simple-form config="my-config">
    <template slot id="header"><div>form header</div></template>
    <template slot id="intro"><div>form intro</div></template>
    <template slot id="startmsg"><div>This is a start message</div></template>
    <span vue-slot="startmsg"><div>This is a start message</div></span>
    <template slot id="endmsg"><div>This is an end message</div></template>
    <span vue-slot="endmsg"><div>This is an end message</div></span>
    <template slot id="footer"><div>form footer</div></template>
</simple-form>

For debugging purposes I’m using the startmsg and endmsg slots in 2 ways: as a template tag (1) and as a named slot (2)

(1) <template slot id="startmsg"><div>This is a start message</div></template>
(2) <span vue-slot="startmsg"><div>This is a start message</div></span>

In the attached screenshot you can see that the startmsg is not injected inside the div tag with id=“startmsg”. Instead, all slots are injected at the end of the component, right before the closing tag </simple-form>

Moreover, when using a template tag (1) the slot content is inserted inside a “#document-fragment”, which is not visible in the browser.

Any ideas what’s happening here?

opera_slots_console

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 3
  • Comments: 20 (5 by maintainers)

Most upvoted comments

I have taken the following workarounds.

// before
Vue.customElement('slot-component', slotComponent);

// after
import { getProps } from 'vue-custom-element/src/utils/props'

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})

Vue.customElement('slot-component', asyncComponentDefinition(slotComponent), {
  props: getProps(slotComponent).camelCase
});

I have taken the following workarounds.

// before
Vue.customElement('slot-component', slotComponent);

// after
import { getProps } from 'vue-custom-element/src/utils/props'

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})

Vue.customElement('slot-component', asyncComponentDefinition(slotComponent), {
  props: getProps(slotComponent).camelCase
});

Wow, awesome to see this community active! Thank you @aki77 , I’ll try that coming week.

@karol-f I just noticed the status of this issue is still “waiting for feedback”. Maybe you can change this to “investigating” or something like that?

Is there a solution for the slots issue on Chrome that doesn’t involve delaying component styles loading?

@aki77 you are awesome!! @karol-f FYI, I have tried this approach and it’s working for me. The following is what I have done successfully

HelloWorld.vue this will go into <slot></slot>

<template>
  <div>
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: { 
    msg: String 
  }
};
</script>

XButton.vue. this has <slot></slot>

<template>
  <div class="host">
    <slot></slot>
    <span> {{title}} {{ count }} {{times}}. </span>
  </div>
</template>

<script>
export default {
  name: 'XButton',
  props: ['title'], // passing data through props
  data: function() {
    return { // data must be a function
      count: 0
    }
  },
  computed: { // getters
    times: function() { 
      return 'times'
    }
  }
};
</script>

<style scoped>
.host {
  display: inline-block;
  border: 1px solid #ccc;
  box-shadow: 4px 4px 8px #ccc;
}
</style>

custom-element-build.js. this uses @kai77 method

import Vue from "vue";
import vueCustomElement from 'vue-custom-element';
import { getProps } from 'vue-custom-element/src/utils/props'
import HelloWorld from '../components/HelloWorld';
import XButton from '../components/XButton';

Vue.use(vueCustomElement);

const asyncComponentDefinition = (component) => () => new Promise((resolve) => {
  setTimeout(() => {
    resolve(component)
  }, 0)
})


Vue.customElement('hello-world', HelloWorld);
// Vue.customElement('x-button', XButton);

Vue.customElement('x-button', asyncComponentDefinition(XButton), {
  props: getProps(XButton).camelCase
});

command

$ vue-cli-service build --target lib --inline-vue --name elementsX custom-element-build.js

html

<body>
  <x-button>
    <hello-world msg="Hello Custom Element"></hello-world>
  </x-button>
</body>

Successful Result withasyncComponentDefinition image

Erroneous Result without asyncComponentDefinition image