Use slot-scoped data in the component script in Vue - javascript

In vue, I have a component A with a slot that returns an object as the slot-scoped in the component B using it:
component A template:
<template>
<div>
<slot :myObject="myObject" />
</div>
</template>
component B template:
<template>
<component-a>
<template slot-scope="{myObject}">
<!-- uses myObject -->
</template>
</component-a>
</template>
<script>
module.exports={
data(){
return {
myObject: null // This never updates with the new value
}
}
}
</script>
Everything works fine in the html template of component B, however, I cannot access to myObject in the script of component B. I could create a child component (C) that accepts myObject as a prop and have all the needed logic there, but I would like to avoid that.

If you use slot-scope you are basically passing data to the slot, from the component, which is hosting the slot (not the with the content of the slot).
In your case if you want to use data slot-scope, you have to pass the data from component A. So the data-source myObject must exist in component A.
So the right approach would look something like this:
Component A
<template>
<div>
<slot :myObject="myObject" />
<button #click="changeMyObject">Change MyObject</button>
</div>
</template>
<script>
export default {
name: "slot-scope-component",
data(){
return {
myObject: {
value: "ABC"
}
}
},
methods:{
changeMyObject() {
this.myObject = {
value: "XYZ"
};
}
}
}
</script>
Component B
<template>
<ComponentA>
<template slot-scope="props">
{{props.myObject}}
</template>
</ComponentA>
</template>
<script>
import ComponentA from '#/components/ComponentA';
export default {
components: {
ComponentA
},
}
</script>
As well there was a little spelling mistake: You wrote slot-scoped instead of slot-scope
You can improve that code further by using destructuring:
slot-scope="{myObject}"

Related

Vue 3 pass data in <slot>

What I am trying to achieve:
Display an image from dynamic link in Post.vue, which follows the layout of PostLayout.vue
In PostLayout.vue, I have a <slot> named postFeaturedImage, and inside the slot, there is a <div>, I want to use the image as the background of it.
What I am using:
Laravel, InertiaJS, Vue 3
My codes are:
Post.vue:
<template>
<PostLayout>
<template #postfeaturedImage>
<!-- Here I want to display the image -->
</template>
</PostLayout>
</template>
<script>
import PostLayout from '#/Layouts/PostLayout'
export default {
data() {
return {
featured_image: ''
}
},
components: {
PostLayout,
},
props: {
post: Object /* Passed the prop from Controller */
},
mounted () {
this.featured_image = this.post.featured_image
}
}
</script>
PostLayout.vue:
<template>
<slot name="postfeaturedImage" :bgImage="bgImage">
<div :style="`background-image:url(${bgImage}); height: 75vh;`"></div>
</slot>
</template>
<script>
export default {
}
</script>
I've removed all irrelevant codes. I am a beginner in Vue 3 and Inertia and in need of help!
An alternative approach will be creating a FeaturedImage component. Also, you can reference the post image directly from the props you receiving. There's no need for the data method and the mounted in this case.
<template>
<PostLayout>
<template #postfeaturedImage>
<FeaturedImage :src="post.featured_image" />
</template>
</PostLayout>
</template>
<script>
import PostLayout from '#/Layouts/PostLayout'
import FeaturedImage from '#/Layouts/FeaturedImage'
export default {
components: {
PostLayout,
FeaturedImage
},
props: {
post: Object
}
}
</script>
Add a props to your PostLayout.vue
<script>
export default {
props: {
bgImage: { type: String },
},
};
</script>
And give a value to that props in your Post.vue
<template>
<PostLayout :bgImage="featured_image"> </PostLayout>
</template>
And if you ever want to have a post without an image and a different layout you should do :
<template>
<PostLayout>
<template #postfeaturedImage> post without background image</template>
</PostLayout>
</template>

How to access slot props in the created hook?

I'm trying to access properties I'm passing on to my slot. But my slotProps are undefined.
As I'm still new to Vue and I've read their docs I still can't seem to figure out why I can't access the props data.
Problem
I'm trying to access the slotProps in my child components created, but it's undefined
emphasized text
<template>
<div>
<slot :data="data" :loading="loading"></slot>
</div>
</template>
Child
<template v-slot:default="slotProps">
<div >
</div>
</template>
<script>
export default {
name: "child"
created: function() {
console.log("slotProps", slotProps);
}
};
</script>
You can use this object to follow with your child property
demo: https://stackblitz.com/edit/vue-kkhwzc?file=src%2Fcomponents%2FHelloWorld.vue
Updated code Child
<template v-slot:default="slotProps">
<div >
</div>
</template>
<script>
export default {
name: "child"
created: function() {
console.log("slotProps", this.slotProps);
}
};
</script>
You do not need the created() life cycle hook to achieve what you want. There are few things to clear up:
What you are using is actually called scoped slots. They are useful because, unlike when using the default and named slots, the parent component can not access the data of its child component(s).
What you call a Child is actually the parent component.
Child.vue component should be something like this:
<template>
<div>
<main>
<slot :data="data1" :loading="loading1" />
</main>
</div>
</template>
<script>
export default {
name: 'Page',
data () {
return {
data1: 'foo',
loading1: 'bar'
}
}
}
</script>
In a Parent.vue component, you can access the data of the above component as follows:
<template>
<child>
<template v-slot="slotProps">
{{ slotProps.data }},
{{ slotProps.loading }}
</template>
</child>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
components: { Child }
}
</script>
Or you can also destruct the objects on the fly as follows:
<template>
<child>
<template v-slot="{data, loading }">
{{ data }},
{{ loading }}
</template>
</child>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
components: { Child }
}
</script>
This is the clean way to access data of a child component from the parent using scoped slots.

Vue.js passing data between components

I want to store input-value from App.vue, and use it in another component. How can I do it? I don't need the show the value in the template, I just need the value inside other components function. In JS I could just use a global var, but how can I achieve it in Vue?
App.vue:
<template>
<div id='app'>
<!-- App.vue has search bar -->
<b-form-input #keydown='search' v-model='input'></b-form-input>
<div>
<!-- Here's my other components -->
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
input: '',
value: ''
}
},
methods: {
search () {
this.value = this.input
this.input = ''
}
}
}
</script>
Another component:
<template>
<div>
<p>I'm another component</p>
<p>App.vue input value was: {{value}} </p>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
value: ''
}
}
}
</script>
This is the basic logic I'm trying to achieve. Input value in App.vue --> anotherComponent.vue
If components are not parent and child you can use store for this:
More advanced vuex store that should be your default GO TO - NPM.
Or simple solution with js object.
a. Create store.js file and export object with property in which you will store value.
b. Import store.js object to vue scripts and use it simply like:
import Store from 'store.js'
Store.value

print Component B inside component A - vue.js

Using Vue.js,
How to create componentA that gets componentB as a prop, and print it inside of it?
example:
index.vue
<template>
<div>
<componentA :componentPlaceHolder="componentB"></componentA>
</div>
</template>
<script>
import componentA from './compnentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {componentA,componentB }
}
</script>
componentA.vue
<template>
<div>
{{componentPlaceHolder}}
</div>
</template>
<script>
export default {
name: 'componentA',
props: {
'componentPlaceHolder': {}
}
}
</script>
There are some issues to your implementation:
You have gotten the scope wrong: componentPlaceHolder lives in the parent scope, not in that of component A. Read: Compilation Scope.
Use :is (i.e. v-bind: is) for dynamic component binding. The data bound should reference the key of the component.
Since you are nested additional components in another component in the same context, that means you have to interweave the content. This is done by using slots, declared in <component-a>.
Avoid using case-sensitive DOM elements, use kebab case instead, i.e. <component-a> instead of <componentA>, since HTML elements are case-insensitive (<componentA> and <componenta> will be treated the same).
Here is the updated code:
<template>
<div>
<component-a>
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
</template>
<script>
import componentA from './componentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
}
</script>
And then in your componentA.vue:
<template>
<div>
<!-- Slot will interweave whatever that is found in <componentA> -->
<slot></slot>
</div>
</template>
<script>
export default {
name: 'componentA'
}
</script>
Proof-of-concept example
If in doubt, here is a live proof-of-concept example:
var componentA = {
template: '#component-a'
};
var componentB = {
template: '#component-b'
};
new Vue({
el: '#app',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
<component-a>
<!-- DOM elements in here will be interweaved into <slot> -->
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
<template id="component-a">
<div>
<p>I am component A</p>
<slot></slot>
</div>
</template>
<template id="component-b">
<p>I am component B</p>
</template>
Footnote:
The VueJS readme is exceptionally composed, and I suggest here are some things that you can read up on that is very relevant to your use case:
Compilation Scope
Dynamic Components
Content Distribution with Slots

vue.js passing data from parent single file component to child

Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}

Categories