Pass in a slot with a reference value? - vue.js

In my component I have this scoped slot:
<slot name="test">
<input ref="inputTest">
</slot>
In the parent I do this:
<div slot="test">
<input ref="inputTest">
</div>
But when I tried to access the ref later in my component:
console.log(this.$refs.inputTest);
I get undefined.
How can I pass in a slot that has a reference?

You can't access refs from parent component to child component.
You can use scoped slot to pass the data between them.
<!-- pass ref as props -->
<slot name="test" :ref="inputTest">
<input ref="inputTest">
</slot>
<!-- receive ref props -->
<template slot-scope="ref">
<!-- bind ref to $refs -->
<input ref="ref">
</div>
It will be confusing obviously. Thus, I would recommend to use any other suitable name for the props instead of ref.

Related

How to access or pass props to child slot in vue

If you have this component hierarchy, is it possible to pass or access isPanelClickable from ComponentPanel in ComponentSomething?
<ComponentA>
<ComponentPanel :isPanelClickable="false">
<ComponentSomething />
</ComponentPanel>
</ComponentA>
ComponentPanel:
<template>
<div class="panel">
<slot /> <!-- Can I "use" 'isPanelClickable' here somehow..? -->
</div>
</template>
This is possible through scoped slots. ComponentPanel could pass a prop to the default slot by binding the prop (e.g., named myProp) on the corresponding <slot> element:
<template>
<slot :myProp="isPanelClickable ? 'I am clickable' : 'I do nothing'" />
</template>
That prop is then passed through the v-slot in the default slot:
<ComponentPanel>
<template v-slot="{ myProp }">
<ComponentSomething :foo="myProp" />
</template>
</ComponentPanel>
demo

How to make a component use v-for have dynamic slots

I have a child component that uses v-for. I want to be able to have the parent pass down a slot, or something similar of how it wants each item in the v-for display. However, the problem is that the parent does not have access to each individual item in the v-for as it's rendered.
Some things i've tried is passing a slot with specific keys. e.g.
<child-comp :items="items">
<div v-text="item.text" slot="body"/>
</child-comp>
Basic code may look like this for what i'm trying (though it doesn't work)
Parent component would look something like
<template>
<child-comp :items="items>
<div v-text="item.text"
</child-comp>
</template>
items = [{ text: 'hello'}]
Child would look something like this
<template>
<div>
<span v-for="item in items">
<slot></slot>
</span>
</div>
</template>
Note this has to be dynamic because one item might do v-text, another may do something like add more html such as an image, and another may do something completely different.
I believe you're looking for scoped slots.
https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
Note that the preferred syntax for using scoped slots changed in Vue 2.6.0 so the exact way you write this will depend on which version you're using.
Your child would pass the item to the slot, like this:
<template>
<div>
<span v-for="item in items">
<slot :item="item"></slot>
</span>
</div>
</template>
The parent would look like this for Vue 2.6.0+:
<template>
<child-comp :items="items">
<template v-slot:default="slotProps">
<!-- The parent can put whatever it needs here -->
{{ slotProps.item }}
</template>
</template>
</child-comp>
</template>
Any props passed to the slot by the child will be included in the slotProps. There's no need to call it slotProps and in practice it is usually destructured (see the docs for more details).
For Vue 2.5 you'd use slot-scope instead:
<template>
<child-comp :items="items">
<template slot-scope="slotProps">
<!-- The parent can put whatever it needs here -->
{{ slotProps.item }}
</template>
</template>
</child-comp>
</template>
Prior to Vue 2.5.0 slot-scope was called scope.

How expose child element v-model as the vue component v-model

I Was using a simple text area in my vue component.:
<input v-model="someRootProperty"/>
I would like to encapsulate this input inside another component, for instance
<template>
<div>
<div>...</div>
<input v-model="???"/>
</div>
</template>
I would like to use
<my-component v-model="someRootProperty" />
instead and them bypass this to the input inside the component.
What should I do inside the component to expose its input v-model as the component v-model?
<input v-model="someRootProperty"/>
Is the same as (syntactic sugar):
<input :value="someRootProperty" #input="someRootProperty = $event.target.value"/>
That means that you could accept value as a prop in the component and emit input to achieve the same thing.
MyComponent.vue
<template>
<input :value="value" #input="$emit('input', $event.target.value)>
</template>
<script>
export default {
props: ['value']
}
And then use it like this.
<MyComponent v-model="someRootProperty"/>

Vue - how to pass down slots inside wrapper component?

So I've created a simple wrapper component with template like:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners"></b-table>
</wrapper>
using $attrs and $listeners to pass down props and events.
Works fine, but how can the wrapper proxy the <b-table> named slots to the child?
Vue 3
Same as the Vue 2.6 example below except:
$listeners has been merged into $attrs so v-on="$listeners" is no longer necessary. See the migration guide.
$scopedSlots is now just $slots. See migration guide.
Vue 2.6 (v-slot syntax)
All ordinary slots will be added to scoped slots, so you only need to do this:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
</b-table>
</wrapper>
Vue 2.5
See Paul's answer.
Original answer
You need to specify the slots like this:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<!-- Pass on the default slot -->
<slot/>
<!-- Pass on any named slots -->
<slot name="foo" slot="foo"/>
<slot name="bar" slot="bar"/>
<!-- Pass on any scoped slots -->
<template slot="baz" slot-scope="scope"><slot name="baz" v-bind="scope"/></template>
</b-table>
</wrapper>
Render function
render(h) {
const children = Object.keys(this.$slots).map(slot => h('template', { slot }, this.$slots[slot]))
return h('wrapper', [
h('b-table', {
attrs: this.$attrs,
on: this.$listeners,
scopedSlots: this.$scopedSlots,
}, children)
])
}
You probably also want to set inheritAttrs to false on the component.
I have been automating the passing of any (and all) slots using v-for, as shown below. The nice thing with this method is that you don't need to know which slots have to be passed on, including the default slot. Any slots passed to the wrapper will be passed on.
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<!-- Pass on all named slots -->
<slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot"/>
<!-- Pass on all scoped slots -->
<template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope"><slot :name="slot" v-bind="scope"/></template>
</b-table>
</wrapper>
Here is updated syntax for vue >2.6 with scoped slots and regular slots, thanks Nikita-Polyakov, link to discussion
<!-- pass through scoped slots -->
<template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
<!-- after iterating over slots and scopedSlots, you can customize them like this -->
<template v-slot:overrideExample>
<slot name="overrideExample" />
<span>This text content goes to overrideExample slot</span>
</template>
This solution for Vue 3.2 version and above
<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope || {}" />
</template>

How do I use the same slot-element for all elements?

So if I have a slot like this:
<span slot="?" slot-scope="data>...</span>
What should question mark be here if I want to use this for everything?
This is not supported by the current state of Vue, nor is this supported when trying to hack this feature in using the JavaScript Proxy class, this is because the internal design of Vue first collects all children and maps them to an object, before passing this to the next component.
You can work around this by specifying your slot contents multiple times, like:
<!-- inside parent -->
<my-child>
<p slot="head">Hello World</p>
<p slot="body">Hello World</p>
</my-child>
Or modifying the child to accept a base slot to use if a slot is not passed in
<!-- inside child -->
<div>
<slot name="head">
<slot name="base/>
</slot>
<slot name="body">
<slot name="base/>
</slot>
</div>