19
votes

I have the following component with a slot:

<template>
    <div>
        <h2>{{ someProp }}</h2>
        <slot></slot>
    </div>
</template>

For some reasons, I have to manually instantiate this component. This is how I am doing it:

const Constr = Vue.extend(MyComponent);
const instance = new Constr({
    propsData: { someProp: 'My Heading' }
}).$mount(body);

The problem is: I am not able to create slot contents programmatically. So far, I can create simple string based slot:

const Constr = Vue.extend(MyComponent);
const instance = new Constr({
    propsData: { someProp: 'My Heading' }
});

// Creating simple slot
instance.$slots.default = ['Hello'];

instance.$mount(body);

The question is - how can I create $slots programmatically and pass it to the instance I am creating using new?

Note: I am not using a full build of Vue.js (runtime only). So I don't have a Vue.js compiler available to compile the template on the fly.

4
I've found in a tutorial for unit tests in a Vue.js application that you can pass a slot argument in the Vue.extend. In this example, the author uses vue-test-utils but I think you can try to add a slots property in your constructor. Source : alexjoverm.github.io/2017/10/02/Test-Vue-js-Slots-in-Jest/…Thomas Ferro
Thanks, @ThomasFerro. I tried this but it doesn't work. It seems to be using some undocumented internal API.Harshal Patil
As I said, he's using vue-test-utils. I'm trying to do the trick with Vue.extend()Thomas Ferro
I'm don't know whether this is the correct way so I only post this as a comment... You can assign Vnodes to $slots.default (I've found this info here css-tricks.com/…) and you can create vnodes with the this.$createElement (vuejs.org/v2/guide/render-function.html) I've modified the example from the CSS tricks example which shows how it can be done: codesandbox.io/s/jl66zxnwz9nemesv

4 Answers

14
votes

I looked into TypeScript definition files of Vue.js and I found an undocumented function on Vue component instance: $createElement(). My guess is, it is the same function that is passed to render(createElement) function of the component. So, I am able to solve it as:

const Constr = Vue.extend(MyComponent);
const instance = new Constr({
    propsData: { someProp: 'My Heading' }
});

// Creating simple slot
const node = instance.$createElement('div', ['Hello']);
instance.$slots.default = [node];

instance.$mount(body);

But this is clearly undocumented and hence questionable approach. I will not mark it answered if there is some better approach available.

4
votes

I think I have finally stumbled on a way to programmatically create a slot element. From what I can tell, the approach does not seem to work for functional components. I am not sure why.

If you are implementing your own render method for a component, you can programmatically create slots that you pass to child elements using the createElement method (or whatever you have aliased it to in the render method), and passing a data hash that includes { slot: NAME_OF_YOUR_SLOT } followed by the array of children within that slot.

For example:

Vue.config.productionTip = false
Vue.config.devtools = false;

Vue.component('parent', {
  render (createElement) {
    return createElement('child', [
      createElement('h1', { slot: 'parent-slot' }, 'Parent-provided Named Slot'),
      createElement('h2', { slot: 'default' }, 'Parent-provided Default Slot')
    ])
  }
})

Vue.component('child', {
  template: '<div><slot name="parent-slot" /><slot /></div>'
})

new Vue({
  el: '#app',
  template: '<parent />'
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id='app'>
</div>
1
votes

(This doesn't really answer How to create Vue.js slot programatically?. But it does solve your problem.)

This trick is less hackish compared to using $createElement().

Basically, create a new component that register MyComponent as a local component.

const Constr = Vue.extend({
  template: `
  <MyComponent someProp="My Heading">
    <div>slot here !!!</div>
  </MyComponent>
  `,
  components: {
    MyComponent: MyComponent
  }
});
const instance = new Constr().$mount('#app');

Demo: https://jsfiddle.net/jacobgoh101/shrn26p1/

0
votes

I just came across an answer to this in vue forum: slots

The principle is: There is nothing like createElement('slot'..) Instead there is a render function which provides the slotted innerHtml as function: $scopedSlots.default()

Usage:

render: function (createElement) {
  const self = this;
  return createElement("div", this.$scopedSlots.default());
}

If you want to provide a default in case there is no content given for the slots, you need to code a disctinction yourself and render something else. (The link above holds a more detailed example)

The function returns an array, therefore it can not be used as a root for the render function. It need to be wrapped into single container node like div in the example above.