Data Binding between parent and child component in vue3 composition api - data-binding

I have successfully bound data in a two way format in vue 3 like this :
<template>
<p for="">Year of Incorporation</p>
<input type="text" v-model="input" />
<div>
{{ input }}
</div>
</template>
<script>
export default {
setup() {
let input = ref("")
return {input};
},
};
</script>
What I want to do now is put the input inside a child component like this:
Parent Component:
<template>
<Child v-model="input" />
{{input}}
</template>
Child Component:
<template>
<input type="text" v-model="babyInput" />
</template>
<script>
export default {
setup() {
let babyInput = ref("")
return {babyInput};
},
};
</script>

The Vue.js documentation presents a nice way of handling v-model-directives with custom components:
Bind the value to the child component
Emit an event when the Child's input changes
You can see this example from the docs with the composition when you toggle the switch in the right-top corner (at https://vuejs.org/guide/components/events.html#usage-with-v-model).

Related

Vue 3 v-model on props

I just started using Vue 3 and I noticed that I cannot use v-model on a prop and I read on how to pass data between Parent<-->Child components via props and emits.
Then I noticed something weird. I cannot use v-model on a prop, but I can use it on a field of the prop.
So this wouldn't word:
<!-- parent.vue -->
<template>
<child :obj="obj"></child>
</template>
<script>
//
data() {
return { obj : "test" }
}
</script>
<!-- child.vue -->
<template>
<input type="text" v-model="obj">
</template>
But this would work
<!-- parent.vue -->
<template>
<child :obj="obj"></child>
</template>
<script>
//
data() {
return { obj : { name: "test" } }
}
</script>
<!-- child.vue -->
<template>
<input type="text" v-model="obj.name">
</template>
So what's the real purpose of not using v-model on a prop?

VUE3.JS - Modal in a loop

I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.

vue3 Cannot loop over an array of object passed as prop

In vue3 I am passing an array of options from parent component to child component in order to use it as options for a select.
At the moment, I am not able to use it to initialize my select.
Here is the child component SmartNumberInput
<template>
<div>
<div>Selected: {{ selected }} Initial:{{ initial }}</div>
{{ options }}
<div v-for="option in options" :key="option.value">
{{ option.text }}
</div>
<input type="number" v-model="input_value" />
<select v-model="selected">
<option
v-for="option in options"
:value="option.value"
:key="option.value"
>
{{ option.text }}
</option>
</select>
</div>
</template>
<script>
export default {
props: ["initial", "options"],
data() {
return {
selected: "",
input_value: "",
};
},
};
</script>
Here is the parent component
<template>
<div>
<h1>Hi! I am the root component</h1>
<div class="smart-number-input">
<smart-number-input
initial="B"
options="[{text:'Liters',value:'A'},{text:'Gallons',value:'B'},{text:'Pints',value:'C'}]"
/>
</div>
</div>
</template>
<script>
import SmartNumberInput from "./SmartNumberInput.vue";
export default {
data() {
return {
initial: "salut",
};
},
components: { SmartNumberInput },
};
</script>
<style>
.smart-number-input {
width: 100%;
background:#EEE;
}
</style>
In the result I get (see picture) there is no option visible in the select though when the small arrow is clicked it expands with a long empty list.
The {{options}} statement in the child displays what I pass as prop i.e. an array of objects but nothing is displayed in the div where I use a v-for loop.
When I declare the options as data in the child both loops (div and select) work fine.
Could somebody explain what is wrong in the way I pass or use the array of options ?
change options to :options (add colon symbol)
.
if you not put colon, it will treat the value as a String...

How to access slot dynamic component in vue3?

// main.vue
<template>
<window v-for="app in apps">
// `app` is defined by `defineAsyncComponent(()=> import('path/to/app.vue'))`
<component :is="app"></component>
</window>
</template>
//window.vue
<template>
<div class="window" #click="click">
<slot/>
</div>
</template>
<script>
...
methods:{
click (){
// I need to access the `app` dynamic component here!
const app = this.$slots.default()[0];
}
}
...
</script>
I dont know how to access <slot/> component which is dynamic loaded.
In above case,this.$slots.default()[0] will be an object and it hasn't contain the dynamic component,just a AsyncComponentWrapper

How can I pass class and style attributes on a vue component to a different element like $attrs?

I have a single file component called confirm-document that looks something like this:
Sandbox
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="$attrs" :class="activatorClass" v-on="on">{{
title
}}</v-btn>
</template>
<v-card>
<v-card-title>{{ title }}</v-card-title>
<v-card-text><slot></slot></v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: "ConfirmDocument",
props: {
title: String,
activatorClass: {},
},
};
</script>
So when I then use this component like:
<ConfirmDocument
class="useless-class"
activator-class="mt-4 ml-n4"
title="Consent"
> Document Content </ConfirmDocument>
Sandbox
The classes get applied to the v-dialog, which ends up as an invisible div with nothing inside and both the activator and modal attached as sibling nodes.
Since this is mainly a wrapper component to provide a consistent UI, I actually only need for the activator to be positionable. So I want to pass the class and style props to the v-activator.
The activator-class prop that I have declared actualy works fine. But I am very curious if there a way to change the element to which the component's class and style attributes are bound, so that I can use class instead?
This is fixed in Vue.js v3 ref
For Vue.js v2, you can try this
Check v-bind and attrs computed property below
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="attrs" v-on="on">{{
title
}}</v-btn>
</template>
<v-card>
<v-card-title>{{ title }}</v-card-title>
<v-card-text><slot></slot></v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: "ConfirmDocument",
inheritAttrs: false, // <- added just for safety
props: {
title: String,
},
computed: {
attrs() {
const attrs = { ...self.$attrs }
// adding class and style for `v-bind`
attrs.class = self.$vnode.data.staticClass
attrs.style = self.$vnode.data.staticStyle
return attrs
}
}
};
</script>
Explanation - In Vue.js version 2.x.x, $attrs does not include class and style (ref) but there are many scenarios in which we wanted to pass on all the props along with class and style into another component so, $attrs should have class and style in it which they did add in version 3 of vue.js.
There is a detailed discussion on this topic on github do check it out for more details.
For version 2, what we wanted to achieve is that class and style can be passed to another component. We can pull it off by getting the class [as string] and style [as object] from the current component node i.e. vnode and pass it on to the other component using v-bind.
Now, you can pass props along with class and style into another component inside ConfirmDocument directly from the parent (or caller) component
What's about simple using props to handle this?
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn :class="btnClass" v-on="on">Read {{ title }}</v-btn>
</template>
<v-card>
<slot></slot>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
btnClass: { type: String },
title: { type: String }
}
}
</script>
and using the component:
<confirm-document
btn-class="mt-0 mb-0"
title="Privacy Policy"
>
Lorem ipsum dolor sit amet
</confirm-document>
I think you can use the inheritAttrs: false property. What it does it to make sure that attributes are not applied automatically to the root element and it lets you choose where to apply them instead.
<template>
<v-dialog>
<template v-slot:activator="{ on }">
<v-btn v-bind="buttonAttrs" >Read {{ title }}</v-btn>
</template>
<v-card>
<slot></slot>
</v-card>
</v-dialog>
</template>
<script>
export default {
inheritAttrs: false,
props: {
title: {type: String},
},
computed: {
buttonAttrs () {
// select which attrs to apply
const { title, ...rest } = this.$attrs;
return rest;
}
}
}
</script>
A working (and a bit cluttered) example can be found here.

Resources