How to access or pass props to child slot in vue - vue.js

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

Related

Slot on Vuetify.js custom component

I have a component which renders a standard .
I would like to use slots from my component, I would like to write something like:
<MyComponent>
<header>
Titolo
</header>
<body>
my component body
</body>
</MyComponent>
then final component should be:
<v-dialog>
<h1>
// header slot content
</h1>
// body slot content
</v-dialog>
how can I do this? This only works with <slot> but not with named slot.
To use multiple slots you can use the following syntax:
<MyComponent>
<template v-slot:header>
Titolo
</template>
<template v-slot:body>
<p>my component body</p>
</template>
</MyComponent>
So you can pass some HTML in the template blocks and it will render in the component.
MyComponent.vue has the next content:
<template>
<v-dialog>
<h1>
<slot name="header"></slot>
</h1>
<slot name="body"></slot>
</v-dialog>
</template>
You can define names for your slots in your custom component by using the name attribute available for the <slot> element, e.g. <slot name="header">. If you don't define a name for the slot, its name will just be default. See the Vue.js slots documentation here: https://v2.vuejs.org/v2/guide/components-slots.html
Also, I made a simple usage example that you can check out here: https://codesandbox.io/s/unruffled-mopsa-f47hm?file=/src/App.vue
So in your case, your custom component could look something like this:
<v-dialog>
<slot name="header" />
<slot name="body" />
</v-dialog>
And to use it in the parent component, you could have:
<MyComponent>
<template v-slot:header>
Titolo
</template>
<template v-slot:body>
<p>my component body</p>
</template>
</MyComponent>

Slot and referring template in the same component in parent

I am stuck with a problem in vue 2. Basically I have a parent and child component. Basically I want to do something like this.
Parent.vue:
<template>
<div>
<Child>
<template #MyComponent>
<slot name="MyComponent" />
</template>
</Child>
<template #MyComponent>
<MyComponent/>
</template>
</div>
</template>
Child.vue:
<template>
<slot name="myComponent"/>
</template>
Can this be done in vue? I have tried to do this. But it doesn't refer to the MyComponent
Thanks in advance.
This is not, how the structure of slots work. You are calling <template #MyComponent> outside of your <Child> and trying to do something there, that is hard to understand.
I´ll asume you try to pass a component named MyComponent inside of the slot of another component named Child. This is a small example for this case:
// Parent, where you call your Child with myComponent in the slot
<Child>
<template #mySlot>
<my-component></my-component>
</template>
</Child>
// Child
<template>
<div>
<slot name="mySlot"></slot>
</div>
</template>
// myComponent
<template>
<div>
Text from myComponent.vue
</div>
</template>

Hoist slot content up one level

I hope someone can help me out with this problem! I'm using a Table component from PrimeVue, and I'm looking to create a wrapper component with a slot for content. The problem is, the component will only recognize content directly within its default slot. Nothing else is recognized.
Code:
<template>
<DataTable :value="data">
<!-- These components are recognized -->
<Column v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
<slot name="override" :cols="cols">
<!-- These components are not -->
<Column v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
</slot>
</DataTable>
</template>
<script>
export default {
data() {
return {
data: // Array of data here,
cols: // Array of cols here
}
}
}
</script>
I checked their implementation, DataTable only looks in $slots.default().children for content. Since the content in <slot name="override"> will show up as something along the lines of $slots.default().children.children, they are not recognized. Is there a way for me to hoist or inject any content (including the default content if possible) from the slot into its parent so the content appears in $slots.default().children?
This template:
<template>
<slot name="heading">
<h1>Default heading</h1>
<h2>Default subheading</h2>
</slot>
</template>
...is effectively the same as conditionally rendering the <slot> with v-if="$slots.heading" (which is only truthy when the heading slot is actually passed in) and moving its inner contents to a v-else block:
<template>
<slot v-if="$slots.heading" name="heading">
</slot>
<template v-else>
<h1>Default Heading</h1>
<h2>Default subheading</h2>
</template>
</template>
So, you can use v-if="$slots.override" to conditionally render the <slot>, and move its contents (i.e., the <Column>s) into a v-else block:
<DataTable :value="data">
<slot v-if="$slots.override" name="override" :cols="cols" />
<Column v-else v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
</DataTable>
demo

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>

Pass in a slot with a reference value?

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.