How do I provide the component that is replacing <slot> with properties? - vue.js

I have a component that functions as a basis for other components.
//The Basis Component lets call it slotComponent
<template>
<div>
//somestuff
<slot :someProperties="someLocalValues"></slot>
</div>
</template>
As you can see I want to give the component that is replacing the slot some Properties that only this component will know.
However if I do this:
//Some page Component lets call it mainPage
<template>
<slotComponent>
<someOtherComponent/>
</slotComponent>
</template>
Then "someOtherComponent" will not have access to "someProperties". How can I provide this component with said property?
Note that someLocalValues are defined in the scope of slotComponent and not its parent. So I cant provide said information in the mainPage.

You need to specify the v-slot directive in order to bind the props. Check the documentation here: https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
You also will need to pass the props to your child component.
In your example, you would need to do something like:
<template>
<slotComponent>
<someOtherComponent v-slot="slotData" :data="slotData.someProperties"/>
</slotComponent>
</template>
This is the same, but with an additional <template> for clarity:
<template>
<slotComponent>
<template v-slot="slotData">
<someOtherComponent :data="slotData.someProperties"/>
</template>
</slotComponent>
</template>

Related

Is there a way to pass elements to a vue component?

when I need to pass some information between child and parent element I use props. But is there a way to pass elements to the component, for example like this
<MyComponent>
<router-link to="/oneOfTheList">OneOfTheList</router-link>
</MyComponent>
Router-Link seems to do it somehow...
How can I specify where the elements will be placed in my component
This is called slot
ComponentA.vue
<div>
<slot></slot>
</div>
ComponentB.vue
<component-a>
<span>test</span> // this part will be shown inside component A's slot tags
</component-a>
Here you go

Passing slots through from Parent to Child Components

I have built a user-defined component (async-select) on top of another component (vue mutliselect) like this:
https://jsfiddle.net/2x7n4rL6/4/
Since the original vue-multiselect component offers a couple of slots, I don't want to loose the chance to use them. So my goal is to make these slots available from inside my custom component. In other words, I want to something like this:
https://jsfiddle.net/2x7n4rL6/3/
But that code oes not work.
However, if I add the slot to the child component itself, it works just fine (which you can see from the fact that options become red-colored).
https://jsfiddle.net/2x7n4rL6/1/
After surfing the web, I have come across this article, but it does not seem to work
Is there any way in VueJS to accomplish this ?
Slots can be confusing!
First, you need a template element to define the slot content:
<async-select :value="value" :options="options">
<template v-slot:option-tmpl="{ props }">
<div class="ui grid">
<div style="color: red">{{ props.option.name }}</div>
</div>
</template>
</async-select>
Then, in the parent component, you need a slot element. That slot element itself can be inside of another template element, so its contents can be put in a slot of its own parent.
<multiselect
label="name"
ref="multiselect"
v-model="localValue"
placeholder="My component"
:options="options"
:multiple="false"
:taggable="false">
<template slot="option" slot-scope="props">
<slot name="option-tmpl" :props="props"></slot>
</template>
</multiselect>
Working Fiddle: https://jsfiddle.net/thebluenile/ph0s1jda/

control over inherited attributes by vue component

Is there a way to have control over attributes provided through the component tag?
For example:
<my-component class="myClass" style="myStyle"></my-component>
My component:
<template>
<div>
<div>
</div>
<div>
</div>
</div>
</template>
At render Vue applies given attributes on the root:
<div class="myClass" style="myStyle">
<div>
</div>
<div>
</div>
</div>
I want to control where those attributes are applied like so:
<div>
<div>
</div>
<div class="myClass" style="myStyle">
</div>
</div>
#Boussadjra Brahim answer is definitely one way to handle it, however this will require you to pass in all of the class attributes you want everytime you define the component.
This question is answered in this SO post already as well.How to style a nested component from its parent component in Vuejs?
If you want a bit more flexibility I would suggested using interpolation and properties as below. This will let you define some default classes and pass in whatever else in addition.
<app-header :headerclass="parent-header-class"> </app-header>
Inside of your child component, you can use these properties and v-bind the class inside the HTML, as shown in the example below:
<template>
<div :class=`${headerClass} internal-class-example button`> </div>
</template>
Note: This does not allow you to use any scoped parent CSS to pass to the child. The classes you pass down must be global. Otherwise, the child component will not know what it is.

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.

vue emit data back to parent when using a slot

I have input on custom component and when i click on the next button on the wrapper component i need to emit details to the parent component.
How is this possible in vue?
wrapper.vue
<template>
<div :id="contentId" class="mt-3">
<div>
<slot></slot>
</div>
<b-row class="float-right">
<b-col>
<button cssClass="e-outline" v-on:click="btnNext">{{nextButtonText}}</button>
</b-col>
</b-row>
</div>
</template>
parent.vue
<template>
<div>
<Wrapper contentId="1">
<CustomComponent1 />
</wrapper>
<Wrapper contentId="2">
<CustomComponent1 />
</wrapper>
</div>
</template>
customComponent1.vue
<template>
<div>
<input v-model="name" />
<input v-model="name2" />
</div>
</template>
code above is for illustrative purposes.
The problem is that the wrapper doesn't innately have access to data of the scoped component, therefore these links have to be created manually. There is no way to tell how many children or slots the component may have, so this kind of functionality is not part of the vue magic.
So in an example where you have parent App component, which holds a Wrapper that has a MyInput component in the slot...
MyInput
The MyInout component doesn't automatically update other components, so it needs to be setup to $emit the internal data.
This can be done using a watch, #change listener for the input, or some other way. You can emit multiple datum as they change, or use a single payload with all the data
this.$emit("input", myData);
App
The App needs to explicitly connect the data between MyInout and Wrapper
<Wrapper> <MyInput #input="onInput" slot-scope="{ onInput }" /> </Wrapper>
The magic/trick happens here, where we bind the input emit function of the input to the onInput function using slot-scope.
Wrapper
The wrapper then needs to listen to the events passed (via App) from Wrapper
<slot :onInput="onInput" />
where onInput is a method that would process the data
see example below
I would recommend the following reading
https://github.com/vuejs/vue/issues/4332 (specifically Evan's response why it's not possible)
https://adamwathan.me/renderless-components-in-vuejs/ Adam has a thoroughly documented way of using render functions and slots to abstract functionality from the UI. While it's not directly related, it's a worthwhile read and may provide more info on using slot-scope as well as some perspective on improving the structure of UI components.