v-model and scoped slots not working? - vue.js

I have a component:
<slot name="test" :name="name">
<input type="text" v-model="name">
</slot>
The input is bound to name in data.
When I use the slot in the parent:
<div slot="test" slot-scope="props">
<input type="text" v-model="props.name">
</div>
Data does not update on the child. It's not linked - why?

What you are seeing is actually the parent's default <input>. So you understand what I mean, add some text to both, like:
<slot name="test" :name="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.name">
</div>
You'll see that what appears is the default.
Now, that happens because, it seems like a bug, when the slot prop has the same name as the parent's, the slot does not work.
Workaround: rename the slot prop.
In the example below, I renamed it from name to namex. Notice the v-model in the default remains the same name because anything in the template refers to the props of that template (in other words, slot props, e.g. namex, will never be available in the parent default slot).
<slot name="test" :namex="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.namex">
</div>

to use v-model in scoped slots, the value of v-model must be one level deeper:
Vue.component('render-props', {
data: () => ({message: 'hello', obj: {msg: 'obj_msg'}}),
template: `<div>
<slot name="a" :message="message">
default: {{message}}
<input v-model="message"/>
</slot>
<slot name="b" :obj="obj">
default: {{obj.msg}}
<input v-model="obj.msg"/>
</slot>
</div>`
});
new Vue({
el: "#root",
template: `<div>
<render-props>
<template v-slot:a="props">
actual: {{props.message}}
<input v-model="props.message"/>
</template>
<template v-slot:b="props">
actual: {{props.obj.msg}}
<input v-model="props.obj.msg"/>
</template>
</render-props>
<cus_2 />
</div>`
});

you are not supposed to modify the data you pass to a slot, pretty
much like a prop. You should pass a method instead to change tha
value. You can always pass an object and modify a property (like a
prop) but it's also not recommended
-from https://github.com/vuejs/vue/issues/9726
So I imagine something like
<template>
<slot v-bind:value="value"></slot>
</template>
<script>
export default {
name: 'FooBar',
data() {
value: '',
},
methods: {
updateValue(e) {
this.value = e.target.value;
}
},
};
</script>
Then when using the component <FooBar> instead of using v-model, you can use the passed scoped slot props, the method (updateValue), and actual prop that will be updated, value.
<FooBar v-slot="slotProps">
<input type="text" :value="slotProps.value" #input="slotProps.updateValue" />
</Foobar>

Related

Is it possible to use a prop as a v-model value?

Is it possible to use the value of a prop as the input's v-model?
I normally do the following when creating an input:
<template>
<form>
<input v-model="form.email" type="email"/>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
}
}
}
}
</script>
But now I'm trying to achieve the following where this.myProp is used within the v-model without being displayed as a string on the input:
<template>
<form>
<input v-model="this.myProp" type="email"/>
</form>
</template>
<script>
export default {
props: ['myProp'] // myProp = form.email for example (to be handled in a parent component)
}
</script>
Yes, but while using it in parent component. In child component you need to extract value and #input instead of using v-model (v-model is shortcut for value="" and #input) Here is an example of input with label, error and hint in Vue 3 composition API.
BaseInput.vue
<template>
<div class="flex flex-col">
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors" class="text-red-400">{{ item.value }}</span>
<span v-if="hint" class="text-sm">{{ hint }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String | Number, errors: Array, hint: String })
defineEmits(['update:modelValue'])
</script>
Using v-bind="$attrs" you target where attributes like type="email" need to be applied in child component. If you don't do it, it will be added to the top level DOM element. In above scenario <div>.
ParentComponent.vue
<BaseInput type="email" v-model="formData.email" :label="Email" :errors="formErrors.email"/>

Dynamic Placeholder in Vue 3 with Global Component

I am trying to set dynamic text for the placeholder attribute on my search bar. Depending on the page, I want the text in the search bar to be different (I will define it in data()).
However, since the search bar component is a global component, it doesn't seem to be editable.
(As you see below is my try, I did it with v-model based on Vue docs, however when I try with placeholder it doesn't work...)
Snippet 1 - Search bar component
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholderValue" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
props: ['modelValue', 'placeholderValue'],
emits: ['update:modelValue', 'update:placeholderValue']
}
</script>
Snippet 2 - Album.vue
<template>
<div class="AlbumView">
<h1>{{header}}</h1>
<h2>{{header2}}</h2>
<br>
<!-- Search Componenet -->
<SearchComponent :placeholder="placeholderValue" v-model="searchQuery" />
<!-- Dynamic Song Route Button -->
<div class="button-container-all mx-5 pb-5">
<div v-for="item in datanew" :key="item.id">
{{ item.album }}
</div>
</div>
</div>
</template>
<script>
import { datatwo } from '#/data2'
export default {
data() {
return {
placeholderValue: "Search for Albums here...",
datanew: datatwo,
searchQuery: null,
header: "Browse by Album",
header2: "Select an Album:",
publicPath: process.env.BASE_URL
};
},
}
</script>
If this is possible?
If you want to do it with v-model (the Childcomponent changes the value of the placeholder) you have to use v-model:placeholder for it to work.
And also placeholderValue is not the way to go the "Value" at the end of a prop is only needed for modelValue which is the default v-model-binding (v-model="") but if you want named v-model-binding (v-model:placeholder="") you do not want to add the "Value" in the props and emits arrays.
Example:
usage of SearchComponent
<SearchComponent :placeholder="'placeholderValue'" v-model="searchQuery" />
instead of 'placeholderValue' you can put any string you want or variable. I just put the string 'placeholderValue' as an example.
SearchComponent
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholder" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
name: "SearchComponent",
props: ['modelValue', 'placeholder'],
emits: ['update:modelValue'],
}
</script>
<style scoped>
</style>

Using parent prop on child function

please help me understand what I'm doing wrong here:
I have a child component that receives a prop from its parent. I want to extract part the first character of that prop value inside the child component, but I'm getting an "undefined" error. Here's the code:
Parent Component:
Child Component:
<template>
<div class="form-group">
<label>{{ label }}</label>
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="text"
id="inputAgno"
v-model="aaaa"
/>
</div>
</div>
</template>
<script>
export default {
props: ["label", "value"],
computed:{
aaaa: function() {
return this.alta_establecimiento.subStr(0,4)
},
}
};
</script>
Thanks in advance!

How to sync v-model to customize Vue component with wrapper?

I am newbie in VueJs.
I want to create customize component with wrapper like this:
template: `<div class="wrapper">
<input name="name" />
</div>`,
when using component, I want to add v-model,
<my-component v-model="form.input" />
But in actually, the value of model is bind just to the wrapper not to the input. If I change the model
form:{ input: "edited" }
that value only bind to wrapper like:
<div class="wrapper" value="edited">
<input name="name" />
</div>
is there any suggestion for my problem.
I am using Vuejs-2.
At minimum, you would need to do something like this:
<div class="wrapper">
<input
:value="value"
#input="$emit('input', $event.target.value)"
/>
</div>
props: ['value']

How to point to wrapping custom element with v-model?

I currently have a custom vue-form component:
In my common HTML it kind of looks something like this:
<vue-form>
<input type="text" v-model="test">
<input type="password" v-model="test2">
</vue-form>
Note that this is not a Vue template.
My v-model keeps pointing to my root component when in this case I would like my v-model to point the the actual component that is wrapping my content.
For the vue-form component I am simply using a slot like so:
<template>
<div>
<slot></slot>
</div>
</template>
Is there a way to get the v-model binding to point towards the wrapping component instead of the root element?
You can use Scoped Slots documentation.
Edit:
Here is working example:
<div id="app">
<vue-form v-bind:model="x">
<template scope="props">
<input type="text" v-model="props.model.test">
<input type="password" v-model="props.model.test2">
</template>
</vue-form>
</div>
new Vue({
el: '#app',
data: function() {
return {
x: {
test: 'John',
test2: 'Smith'
}
}
},
components: {
'vue-form': {
template: `<div><slot :model="model"></slot></div>`,
props: ['model']
}
}});
jsfiddle
Just notice that this solution requires from you to propagate property from component to slot by :model="model" part in slot declaration.