Below is my current structure (which doesn't work).
Parent component:
<template>
<field-input ref="title" :field.sync="title" />
</template>
<script>
import Field from './input/Field'
export default {
components: {
'field-input': Field
},
data() {
return {
title: {
value: '',
warn: false
}
}
}
}
</script>
Child component:
<template>
<div>
<input type="text" v-model="field.value">
<p v-bind:class="{ 'is-invisible' : !field.warn }">Some text</p>
</div>
</template>
<script>
export default {
props: ['field']
}
</script>
The requirements are:
title.warn
value changes in parent, the child's class
bind should be updated (field.warn
).<input>
is updated (field.value
), then the parent's title.value
should be updated.What's the cleanest working solution to achieve this?
Vue is also perfectly capable of powering sophisticated Single-Page Applications in combination with modern tooling and supporting libraries. The v-model directive makes two-way binding between a form input and app state very easy to implement.
In Vue, two-way binding is accomplished using the v-model directive.
Two-way binding gives components in your application a way to share data. Use two-way binding to listen for events and update values simultaneously between parent and child components. See the live example / download example for a working example containing the code snippets in this guide.
To specify the type of prop you want to use in Vue, you will use an object instead of an array. You'll use the name of the property as the key of each property, and the type as the value. If the type of the data passed does not match the prop type, Vue sends an alert (in development mode) in the console with a warning.
There are several ways of achieving two-way data binding:
For two-way bindings keep in mind that it can cause a chain of mutations that are difficult to maintain, quoted from the docs:
Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child.
Here are some details to the methods that are available:
1.) Use props on components
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component
Props are easy to use and are the ideal way to solve most common problems.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the child, it is changed in the parent.
If you pass simple values (strings, numbers) via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use Vuex
Vuex is a state manager that is accessible from every component. Changes can be subscribed to.
By using the Vuex store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
See the getting started guide
Don't bind the child component's <input>
to the parent's title.value
(like <input type="text" v-model="field.value">
). This is a known bad practice, capable of making your app's data flow much harder to understand.
The requirements are:
- If parent's data
title.warn
value changes in parent, the child'sclass
bind should be updated (field.warn
).
This is simple, just create a warn
prop and pass it from parent to child.
Parent (passing the prop to the child):
<field-input ref="title" :warn="title.warn" />
Child/template (using the prop -- reading, only):
<p v-bind:class="{ 'is-invisible' : !warn }">Some text</p>
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {warn: Boolean}
}
Notice that in the template it is !warn
, not !title.warn
. Also, you should declare warn
as a Boolean
prop because if you don't the parent may use a string (e.g. <field-input warn="false" />
) which would yield unexpected results (!"false"
is actually false
, not true
).
- If the child's
<input>
is updated (field.value
), then the parent'stitle.value
should be updated.
You have a couple of possible options here (like using .sync
in a prop), but I'd argue the cleanest solution in this case is to create a value
prop and use v-model
on the parent.
Parent (binding the prop using v-model
):
<field-input ref="title" v-model="title.value" />
Child/template (using the prop as initial value and emitting input
events when it changes):
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {value: String}
}
Click here for a working DEMO of those two solutions together.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With