Vue.js 2 - v-model on component - vuejs2

I have the following component:
Vue.component('test-component',{
template: `<div>
{{value}}
<button on:click="updateValue();">update</button>
</div>`,
props: ['value'],
methods: {
updateValue(){
this.$emit('input', this.value + "X");
}
}
});
Instantiated as follows, (bound to a data variable 'testValue):
<test-component v-model="testValue"></test-component>
The code is intended to add an 'X' at the end of the bound value upon clicking the button.
My intention is to create a reusable component that can be bound to data in its enclosing components, in order to for example create custom form inputs.
It does not work - the emit does not seem to do anything. What am I doing wrong?

You need to use v-on instead of just on in following line, which will bound the vue methods to the button:
template: `<div>
{{value}}
<button v-on:click="updateValue();">update</button>
</div>`,
Working fiddle.

Related

Vue mutate prop binded by v-bind with sync modifier

I have an object in my component data. Now, I'm just binding all the properties of the object as a prop to the child component using v-bind.sync directive. I'm updating these props from the child component using the built-in update event but still, I'm getting Avoid mutation props directly error in the console. Here is the minimal example attached.
Parent Component
<template>
<div>
<oslo v-bind.sync="data" />
</div>
</template>
<script>
import Oslo from '#/components/Oslo.vue'
export default {
components: {
Oslo,
},
name: 'OsloParent',
data() {
return {
data: {
data: {
name: 'Oslo name',
access: 'admin'
}
},
}
},
}
</script>
Child component
<template>
<div>
<input type="text" v-model="name" #keyup="$emit('update:name', name)" />
<input type="text" v-model="access" #keyup="$emit('update:access', access)" />
</div>
</template>
<script>
export default {
props: {
name: String,
access: String
},
name: 'Oslo',
}
</script>
This is just an example component I've created for the reproduction of the problem. The actual component is supposed to handle so many props with two-way binding and that's the reason I'm binding the data with v-bind directive with sync modifier. Here is the Vue warning from the console (most common).
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "name"
Any suggestions to improve this or silent the Vue warn for this specific case? The above-given components works as desired, Vue throws error though.
I found two problems with your example that might throw this off.
The use of v-model directly to the property. Use v-bind instead to have it only display. And use v-on:change handler to fire the $emit('update:propertyname', value) and send the new value to update on the object.
The value sent along in the $emit seems empty and thus makes no change. Use $event.target.value instead.
Side note: v-on:keyup might not be the best event to listen to, since input can also be drag-and-dropped. Listening to v-on:change would be beter in that case.
Note on event listeners when using only v-bind.sync instead of v-bind:propertyName.sync:
If you want to listen to the update:propertyName event from the child component on the parent, you have to use the .capture modifier. Otherwise the update event is caught by the v-on:update:propertyName on the child component and this does not bubble up to the parent.
So you can use v-on:update:name.capture="someMethod" on the <oslo> tag for example. And have this someMethod in the parent's methods. After this is called, the event will be triggered on the child component which will update the object and thereby the property.
All together:
let Oslo = {
props: {
name: String,
access: String
},
name: 'Oslo',
template: `<div>
<input type="text" :value="name" #change="$emit('update:name', $event.target.value)" />
<input type="text" :value="access" #change="$emit('update:access', $event.target.value)" />
</div>`
}
new Vue({
el: "#app",
components: {
Oslo,
},
data: {
thedata: {
name: 'Oslo name',
access: 'admin'
}
},
methods: {
nameWillBeUpdated: function(v) {
console.log('New value of name will be:', v);
// After this, the `update:name` event handler of the
// child component is triggered and the value will change.
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<span>{{this.thedata.name}} - {{this.thedata.access}}</span>
<oslo
v-bind.sync="thedata"
v-on:update:name.capture="nameWillBeUpdated"
/>
</div>
You can just pass an object and sync it instead of individual properties if you have many properties to listen to from child component. See the example below:
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.component('Oslo', {
template: `
<div>
<input type="text" v-model="comp_name" #keyup="$emit('update:name', comp_name)" />
<input type="text" v-model="comp_access" #keyup="$emit('update:access', comp_access)" />
</div>
`,
props: {
data: {
name: String,
access: String,
}
},
data() {
return {
comp_name: this.data.name,
comp_access: this.data.access
}
}
})
new Vue({
el: '#app',
data() {
return {
doc: {
name: 'Oslo name',
access: 'admin'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<span>---- {{ this.doc.name }}----</span>
<span>---- {{ this.doc.access }}----</span>
<oslo :data="this.doc" v-bind.sync="doc" />
</div>
</div>

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

Undestanding Custom Component and V-Model in VueJS

NOTE: I have made a video version of this quesiton, which you can view here:
https://www.loom.com/share/6a23d0791c2444068e964b388ed5497e
The VueJS documentation dicusses how to use v-model with components: https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
If I create a component exactly has written in the documentation, it works just fine. Here is the code:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
But now let me try and change the name of the prop in that component -- for example, to bar:
Vue.component('custom-input', {
props: ['bar'],
template: `
<input
v-bind:value="bar"
v-on:input="$emit('input', $event.target.value)"
>
`
})
Now it only half-works. That is to say, let's say I am binding custom-input to a data property called message, like this: <custom-input v-model="message"></custom-input>. If I dynamically update the value of message, then the value of custom-input will update -- but only once. If I update the value again it will not update the value of custom-input.
However, if I change the prop name back to value (instead of bar), then the value of custom-input will update each and every time I udpate the value of message.
[again, here is the video description of what I am talking about: https://www.loom.com/share/6a23d0791c2444068e964b388ed5497e]
Why is that? Why should it matter what name I give the prop? Does it have to be named value because I am binding to the value attribute? If so, why?
In short, what is going on here?
Thanks.
Yes, the attribute must be named value because you are using v-model to bind. v-model is an abbrevistion of binding the attribute value and listening to the event input. It's a special use case for binding a value two ways.
So, this is exactly the same:
<custom-input v-model="user" />
And:
<custom-input :value="user" #input="user = $event" />
If you prefer the full attribute notation:
<custom-input v-bind:value="user" v-on:input="user = $event" />
With the variable $event, you can, directly in the template, access the emitted value. You can also write a function name without brackets into the template to pass the emitted value as first parameter (e.g. #input="setUser", then declare a method setUser(user)).
props is used to get data down from a parent component to a child component
$emit is used to send back data from the child to the parent
v-model is used for 2 way data binding
always make the data reactive with a return data(){return{...}}
props example:
Vue.component('product',{
template:`
<div>
<item :items="items"/>
</div>
`,
data() {
return{
items:'hi'
}
}
})
Vue.component('item',{
template:`
<div>
{{items}}
</div>
`,
props:{
items:{
type:String,
required:false
}
}
})
var app= new Vue({
el:"#app"
})
//in html file
<div id="app">
<product/>
</div>

What is the correct way to retrieve data from 2 or more identical components?

Evening. I've created a button which adds a component that has an input field inside. I might need to press that button few times so there would be 2-3 input fields that appear. Whenever I type the text I would like to send a request from the parent component but I don't know how to retrieve the data from every child component that has been created. Is this the time to start using vuex (never used it)?
ParentComponent.vue
<template>
<div>
<button class="btn btn-success" #click="addStep">Add step</button>
<div v-for="i in count">
<recipe-step v-bind:step-number="i"></recipe-step>
</div>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addStep() {
this.count += 1;
}
}
}
</script>
StepComponent.vue
<template>
<div>
<div class="from-group">
<label for="step-input"></label>
<input id="step-input" v-model="text" type="text">
</div>
</div>
</template>
<script>
export default {
props: {
stepNumber: {
type: Number,
required: true
}
},
data() {
return {
step: this.stepNumber,
text: ""
}
}
}
</script>
No, you really don't need Vuex yet. As long as you are still dealing with parent-child-component communication, you should be fine. Vuex comes into play when components, spread across the hole component hierarchy, need to exchange information.
Now, you should do something like this:
Don't store the text in the child component. When the input changes, send a Custom Event right to the parent component. Note that
<input v-model="text">
is only syntax sugar for
<input :value="text" #input="text = $event">
Both have the same effect. That's way you can send the input event up to the parent, like this:
<input #input="$emit('input', $event)">
Add another prop to your child component called value which should replace text.
And now you can use v-model in the parent component:
<recipe-step v-model="text">
To store multiple values, just use an array in your data properties.
<recipe-step v-model="textArray[i]">
Vuex can help you on that, however if all you want is to get the input text value back to the parent with the minimum effort you can create a prop called value in the children and then pass it as v-model in the parent.
Since you have a v-for you could make it iterate over a list instead a counter and then pass some prop inside each item as v-model

How split vue single components template section in to smaller subtemplates

My application is being build on vuejs#2 has multiple forms most of the share same html template with add and reset button. As well as same method, resetForm nullifies the "item" property and resets the form, and create method sends the item to the backend.
<div class="row">
<div class="action">
<button class="btn btn-white" #click="create()">✎ Add</button>
<button class="btn btn-white" #click="resetForm()">❌ Reset</button>
</div>
</div>
I can share methods via mixins with each component but I can't share "template partial" same way. How to you approach such scenario?
I tried to create component create-reset-buttons, but I have no way to trigger parent method as each component encapsulates its functionality and does not allow to modify props from the child. Which need to be done in order to reset the parent form.
Components are not allowed to modify the props, but there are ways child can communicate to parent as explained here in detail.
In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.
How to pass props
Following is the code to pass props to chile element:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
How to emit event
HTML:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
JS:
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})