Passing value from parent to child using slot probably - vue.js

I probably don't understand how it should be done. I spent a few hours to achieve this functionality but with no luck. Here is what I have:
Child
<template>
<div>
Data from dialog: {{aaa}}
</div>
</template>
<script>
export default {
name: 'frm',
props: [
'aaa'
]
}
</script>
Parent:
<template>
<div>
<slot :aaa="some"></slot>
</div>
</template>
<script>
export default {
name: 'dlg',
data: () => ({
some: 'data from dialog'
})
}
</script>
View:
<template>
<div>
<dlg>
<frm></frm>
</dlg>
</div>
</template>
<script>
import Dialog from '#/components/dialog.vue'
import Frm from '#/components/frm.vue'
export default {
name: "View",
components: {
'dlg': Dialog,
'frm': Frm
}
};
</script>
Edit: Real code
dialog-template:
<template>
<v-dialog
v-model="internal.dialogOpened"
>
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form /> <!-- just regular component in which I want to get value passed through slot in dialog-template -->
</dlg-template>
</template>
<script>
import DlgTemplate from '#/components/site/dialogs/dialog-template.vue'
export default {
// ...
components: {
'dlg-template': DlgTemplate,
'task-details-form': DetailsForm,
},
I want to avoid passing prop in View but I don't know how :/ I've read about 'slot-scope' unfortunately with no success. How to achieve such functionality?

Edit: real code
Based on your real world code, you were only missing the attachment of the scope, see below.
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }">
<!-- you can use the var `aaa` here -->
</task-details-form>
</dlg-template>
</template>
I'd still wager if you want to use aaa inside task-details-form component you have to pass it down as a prop. But it looks wierd to me, because I'm unsure of the execution order right now (v-slot vs v-bind), but try it like this:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }" :your-a-prop-name="aaa" />
</dlg-template>
</template>
Edit 2: after testing
v-bind shorthand is not working on <slot>:
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot v-bind:aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<template v-slot="{ aaa }"> <!-- has to preceed v-bind -->
<task-details-form :propertyOnComponent="aaa" /> <!-- now you cand bind it -->
</template>
</dlg-template>
</template>
Original:
I think you misunderstood slots. Check the docs:
That slot has access to the same instance properties (i.e. the same “scope”) as the rest of the template. The slot does not have access to child’s scope.
Slots
So you could do that, but then your code becomes:
<template>
<div>
<dlg>
<frm>
<child :aaa=“dialogData” />
</frm>
</dlg>
</div>
</template>
Frm:
<template>
<div>
From component
<slot></slot>
</div>
</template>
If you would define a slot on frm as you are suggesting, you would be able to fill that slot with a specific template and you can receive(!) the scope for that slot.
The deprecated slot-scope which you mentioned would provide you with a bound context from the child to be exposed in your overriding slot in the parent, that’s the opposite of what you want.
Just out of curiosity, why not send down the dialog data to the form as a property? That’s exactly what it’s for.

Related

Vue layout with named slots

Is it possible?
I have something like this:
<template>
<my-layout>
<template #header>
Some header html goes here
</template>
Body html here
</my-layout>
</template>
<script>
import MyLayout from './MyLayout.vue'
export default {
layout: MyLayout,
components: {
//MyLayout
}
}
</script>
And template like that
<template>
<div>
<slot name="header"/>
</div>
<slot/>
</template>
The default slot works, but "header" slot doesn't display itself (unless using MyLayout as standard component).
I believe there is a problem with the closing tags on the template you have given and it should be like the following:
<template>
<div>
<slot name="header"> </slot>
</div>
</template>
After that, layout property only excepts strings or functions that return strings, so parent should be like the following:
<template>
<my-layout>
<template #header>
Some header html goes here
</template>
Body html here
</my-layout>
</template>
<script>
import MyLayout from "./MyLayout.vue";
export default {
layout: "MyLayout",
components: {
//MyLayout
}
};
</script>
I hope this solves your problem and have a nice day :)

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

My dynamic component (layout) doesn't work with named slots in vuejs

I have problems to combine dynamic generated layouts with named slots.
To define my layouts I'm using "component :is"
//app.vue
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script>
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
},
</script>
//layouts/default.vue
<template>
<div>
<div>
<slot name="header" />
</div>
<div>
<div>
<slot name="sidebar" />
</div>
<div>
<slot name="default"/>
</div>
</div>
</div>
</template>
// views/page.vue
<template>
<div>
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</div>
</template>
But now I get this error inside my code
'v-slot' directive must be owned by a custom element, but 'div' is not.
and console displays this error
<\template v-slot> can only appear at the root level inside the receiving component
If I remove the main div I get the error
The template root requires exactly one element.
What I'm doing wrong?
This is not easy to explain so please cope with me...
I really understand what you are trying to do but unfortunately it is not possible in Vue.
Reason for that is slots are more template compiler feature than runtime feature of Vue. What I mean by that ? When Vue template compiler sees something like <template #header>, it will take the inner content and compile it into a function returning virtual DOM elements. This function must be passed to some component which can call it and include the result in it's own virtual DOM it is generating. To do that template compiler needs to know to what component it should pass the function (that is the real meaning of 'v-slot' directive must be owned by a custom element, but 'div' is not. error message...ie compiler is "looking" for a component to pass the slot content to...)
But you are trying to use the slots as if they were "discoverable" at runtime. For your code to work the dynamic layout component must at runtime somehow discover that it's child (also dynamic thanks to <router-view />) has some slot content it can use. And this is not how slots work in Vue. You can pass the slot content your component receives from parent to a child components but do not expect that parent component (layout in this case) can "discover" slot content defined in it's child components...
Unfortunately only solution for your problem is to import the layout component in every "page" and use it as a root element in the template. You can use mixins to reduce code duplication (to define layout computed)
#/mixins/withLayout.js
export default = {
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
}
}
views/page.vue
<template>
<component :is="layout">
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</component>
</template>
<script>
import withLayout from '#/mixins/withLayout'
export default {
mixins: [withLayout]
}
</script>

Deep in slots vue 2

Does anybody know is there are some limitations on how deep my components with slots can be ?
Now I have 3 components, like
list.vue
<div class="list">
<slot name="list" />
</div
wrapper.vue
<list>
<template #list>
<div>hello</div>
<slot name="wrapper" />
</template>
</list>
last.vue
<wrapper>
<template #wrapper>
<search :value=value />
</template>
</wrapper>
So, i want to transfer some value from last component to search component. On init something is good, but if value is changing in my last component my search component it doesn't see.
Maybe someone know information about max deep for components + slots ?
You forgot to share your Search component and the rest of the other components (namely, the script section). There is no limit in the slot depth (whatever that means). I'm assuming you have some other errors in parts you haven't shared. My first guess would be the way you have set the props in your Search component.
Here is an example of a working version on how you should set a nullable prop on a component:
Last.vue
<template>
<Wrapper>
<template #wrapper>
<label>
<input type="text" v-model="value">
</label>
<Search :value=value />
</template>
</Wrapper>
</template>
<script>
import Wrapper from "#/components/Wrapper";
import Search from "#/components/Search";
export default {
components: {Search, Wrapper},
data() {
return {
value: null
}
}
}
</script>
Search.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
}
},
}
</script>
If this doesn't address your problem, please share the rest of your code and I'd be glad to help!

How do I define a component in a slot which as a prop which is defined in the child component

Let's say I have three components: Alpha, Bravo and Charlie.
Which looks like this:
Alpha.vue
<template>
<div class="alpha">
<bravo>
<template slot="card">
<charlie></charlie>
</template>
</bravo>
</div>
</template>
Bravo.vue
<template>
<div class="bravo">
<slot name="card" v-for="result in results" :result="result"></slot>
</div>
</template>
<script>
export default {
data: function() {
return {
results: [1, 2, 3]
}
}
}
</script>
Charlie.vue
<template>
<h1>{{ result }}</h1>
</template>
<script>
export default {
props: [
'result'
]
}
</script>
How can I pass the result prop to Charlie while defining it in a slot in Alpha?
The idea behind this is that Bravo contains a lot of shared logic. There are different variations of Alpha which may contain a different card for the slot (but will always have a result prop.)
At the moment when running that, the result prop is not being parsed to the Charlie component and an undefined error is occurring (there are probably several wrong things with the example but I hope it demonstrates what I'm trying to achieve.)
I think this is solution for your case
Alpha.vue
<template>
<bravo>
<template slot="card" slot-scope="{ result }">
<charlie :result="result"></charlie>
</template>
</bravo>
</template>
And you should wrap your slots in Bravo.vue
Documentation