Passing parents props to slot in Vue,js - vue.js

I have in my application a DocumenContainer component which has multiple ChartContainer components. The ChartContainer has a slot in which I put various types of Charts (bar chart, Line Chart etc.). I would like to pass the data isOuput to the child component which is a slot
ChartContainer (simplified):
<template>
<div class="card-body">
<slot v-slot="isOutput"></slot>
</div>
</template>
<script>
export default {
data() {
return {
isOutput : false,
}
}
</script>
DocumentContainer:
<chart-container title="Stats Model" v-slot="slotProps" :documentId="id">
{{slotProps.isOuput}}
<v-bar-chart :docId="id"></v-bar-chart>
</chart-container>
I tried passing the isOutput to the parent (DocumentContainer) with v-slot. The problem right now is that I'm only able to print {{slotProps.isOutput}}. I would like to pass that slotProps.isOutput as a props to the <v-bar-chart> and
<v-bar-chart :isOuput="slotProps.isOutput" :docId="id"></v-bar-chart>
is giving me undefined in the bar-chart props.
Is there a simpler way than to pass the data to the parent and to the child? How can I achieve this?

I think this is something to do with the context
It will work if you use v-bind instead
<v-bar-chart v-bind="{ isOutput: slotProps.isOutput, docId: id }"></v-bar-chart>
Example code
const Component1 = {
template: `
<div>
<h2>Component 1</h2>
<button #click="isOutput = !isOutput">Toggle</button>
<slot :isOutput="isOutput"></slot>
</div>
`,
data() {
return {
isOutput: false,
}
}
};
const Component2 = {
props: ['isOutput'],
template: `
<div>
<h2>Component 2</h2>
isOutput: {{String(isOutput)}}
</div>
`
};
new Vue({
el: '#app',
components: {
Component1,
Component2
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<h1>Home</h1>
<Component1>
<template v-slot="slotProps">
isOutput: {{String(slotProps.isOutput)}}
<Component2 v-bind="{ isOutput: slotProps.isOutput }">
</Component2>
</template>
</Component1>
</div>

Related

Vue.js Passing a variable in a loop to another component

As a beginner in Vue.js,I am trying to create a Todo app, but problems seems to passing a variable to another component when looping.
i want to pass item to another embedded component.
Here what I have with all the components:
in main.js:
import Vue from "vue";
import App from "./App.vue";
import Todo from "./components/Todo";
import itemComponent from "./components/itemComponent";
Vue.config.productionTip = false;
Vue.component('todo', Todo);
Vue.component('itemcomponent', itemComponent);
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
export default {
name: "App",
components: {
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="item.id">
<itemcomponent {{item}}></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
components: {
itemcomponent,
},
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
};
</script>
in itemComponent.vue:
<template>
<div>
<input type="text" value={{item }}/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>
what is my mistake? thanks for help.
Quite a bunch of mistakes:
You should import single page components inside their parent components and register them there, not in main.js file. EDIT: Explaination to this is given in docs
Global registration often isn’t ideal. For example, if you’re using a build system like Webpack, globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download.
You have a component registration in Todo.vue, but you have misplaced it inside data so its is just a data object that is not getting used, move into components.
In your loop you have item.id, but your item is just a plain string, it does not have an id property. Either change item to object with id property, or simply use the index provided in loop (not recommended).
Pass your item as a prop, not mustache template. So in Todo.vue replace {{ item }} with :item="item"
In your ItemComponent.vue you have mustache syntax once again in the attribute. Change value={{item }} to :value="item"
So here's the final code:
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
import Todo from './components/Todo.vue';
export default {
name: "App",
components: {
Todo,
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="index">
<itemcomponent :item="item"></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
components: {
itemComponent,
}
};
</script>
itemComponent.vue:
<template>
<div>
<input type="text" :value="item"/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>

VuesJS components template

I'm a VueJS beginner and i'm struggling to understand some component logic.
If i have my component (simplified for clarity) :
Vue.component('nav-bar', {
template: '<nav [some code] ></nav>'
}
This component represent the whole navigation bar of my page.
In my HTML file, how can i insert code inside the component?
Something like:
<nav-bar>
<button></button>
...
</nav-bar>
Could you please tell me if it is the right way to do it?
There are at least three options I can think of:
Using ref, or
Slot props with scoped slots, or
provide/inject.
1. Example with ref
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar ref="navbar">
<button #click="$refs.navbar.run()">Run with refs</button>
</nav-bar>
</div>
2. With Scoped <slot>
Vue.component('NavBar', {
template: `
<nav>
<slot v-bind="$options.methods"></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template #default="methods">
<button #click="methods.run">Run with slot props</button>
</template>
</nav-bar>
</div>
3. With provide and inject
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
provide() {
const props = {
...this.$options.methods,
// The rest of props you'd like passed down to the child components.
};
return props;
},
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
// In order to "receive" or `inject` the parent props,
// the child(ren) needs to be a component itself.
Vue.component('Child', {
template: `
<button #click="run">
<slot></slot>
</button>
`,
// Inject anything `provided` by the direct parent
// This could also be `data` or `props`, etc.
inject: ['run']
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template>
<child>Run with injected method</child>
</template>
</nav-bar>
</div>

Vue. Change the contents of one slot from another

I have a component with certain slots.
<v-split-container ref="splitContainer" class="panel-panel">
<template slot="left">
Test
</template>
<template slot="right">
<router-view></router-view> //Show Compontent A
</template>
<template slot="bottom"> //Slot B
Hello
</template>
</v-split-container>
Can I, from component A, change the contents of slot B by calling a function inside the component?
Hi you can do it with Scoped Slot. I have created an example for you how to do it. Please not that I am using v-slots(Some context here) only usable from vue v2.6.
Please take a look at the code example: https://codesandbox.io/s/2030jo20j0?fontsize=14
Child Component
<template>
<div>
<div>
<slot name="msgSlot">{{msg}}</slot>
</div>
<br>
<slot name="bottom" :updateMsg="updateMsg"></slot>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data: () => ({
msg: "Default Message"
}),
methods: {
updateMsg(text) {
this.msg = text;
}
}
};
</script>
Parent Component
<template>
<div id="app">
<HelloWorld>
<template v-slot:msgSlot></template>
<template v-slot:bottom="{updateMsg}">
<input type="text" v-model="msg">
<button #click="updateMsg(msg)">Change Msg</button>
</template>
</HelloWorld>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
data: () => ({
msg: ""
}),
components: {
HelloWorld
}
};
</script>

Using v-model inside scoped slots

I'm using Vue 2.6.9 with the new v-slot syntax. I want to access interact with v-model inside slot. The problem is that showing data inside slot works, but using v-model does not. Here is my code:
Vue.component('base-test', {
template: `
<div>
<slot :foo="foo" :foo2="foo2"></slot>
</div>
`,
data(){
return{
foo: 'Bar',
foo2: 'Bar 2'
}
}
});
// Mount
new Vue({
el: '#app'
});
<div id="app">
<base-test v-slot="sp">
<div>foo2 is {{ sp.foo2 }}</div>
<input type="text" v-model="sp.foo">
<div>foo is {{ sp.foo }}</div>
</base-test>
</div>
Codepen
My question is how to interact with the component data from within slot.
Regarding this issue, Vue core member says that you should not modify the data you pass to a slot.
You should pass a method to change the value. If you agree with this, then follow this answer.
However, there is a tricky way to do it by taking advantage of Javascript reference values
Instead of passing a primitive value (which will not have reactivity), you pass a reactive object which will keeps its reactivity (eg Observer, reactiveGetter, reactiveSetter).
Child component
<template>
<div class="child">
<slot :model="model"></slot>
</div>
</template>
<script>
export default {
data: () => ({
model: {
value: "Initial value",
},
}),
};
</script>
Parent component
<template>
<div id="app">
<Child v-slot="{ model }">
<input type="text" v-model="model.value" />
</Child>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child,
},
};
</script>
See it live on codesandbox.
Ok, it seems that one cannot change the data directly. The way to do it is to pass as slot prop method and basically redo v-model:
<div id="app">
<base-test v-slot="sp">
<div>foo2 is {{ sp.foo2 }}</div>
<input type="text"
:value="sp.foo2" #input="event => sp.onInput(event, 'foo2')">
<div>foo is {{ sp.foo }}</div>
</base-test>
</div>
Vue.component('base-test', {
template: `
<div>
<slot :foo="foo" :foo2="foo2" :onInput="onInput"></slot>
</div>
`,
data(){
return{
foo: 'Bar',
foo2: 'Bar 2'
}
},
methods:{
onInput(event, prop){
this[prop] = event.target.value;
}
}
});
// Mount
new Vue({
el: '#app'
});
Codepen demo

Reusable nested VueJS components

Is it possible to declare a component inside another component in Vue.JS?
this is what i'm trying to do:
<!-- this is declared inside some-component.vue file -->
<script>
export default {
components:{
'cmptest' : {
template:'#cmptest',
props:['mprop']
}
},
data : () => ({
val:'world'
})
};
</script>
<template>
<div>
<template id="cmptest">
{{mprop}}
</template>
<cmptest mprop="hello"></cmptest>
<cmptest :mprop="val"></cmptest>
</div>
</template>
I'd like to avoid globally registering the child component if possible (with Vue.component(...))
In other words, I'd like to specify child's <template> inside the parent component file (without doing a huge line template:'entire-html-of-child-component-here')
Sure.
Like this:
https://jsfiddle.net/wostex/63t082p2/7/
<div id="app">
<app-child myprop="You"></app-child>
<app-child myprop="Me"></app-child>
<app-child myprop="World"></app-child>
</div>
<script type="text/x-template" id="app-child2">
<span style="color: red">{{ text }}</span>
</script>
<script type="text/x-template" id="app-child">
<div>{{ childData }} {{ myprop }} <app-child2 text="Again"></app-child2></div>
</script>
new Vue({
el: '#app',
components: {
'app-child': {
template: '#app-child',
props: ['myprop'],
data: function() {
return {
childData: 'Hello'
}
},
components: {
'app-child2': {
template: '#app-child2',
props: ['text']
}
}
}
}
});