Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue two way prop binding

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:

  • If parent's data title.warn value changes in parent, the child's class bind should be updated (field.warn).
  • If the child's <input> is updated (field.value), then the parent's title.value should be updated.

What's the cleanest working solution to achieve this?

like image 438
Diolor Avatar asked Feb 25 '18 23:02

Diolor


People also ask

Does Vue have 2 way binding?

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.

Which Vue directive is used for two way binding?

In Vue, two-way binding is accomplished using the v-model directive.

What is 2way binding?

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.

How do you use props in Vue 2?

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.


2 Answers

There are several ways of achieving two-way data binding:

  1. Use props on components
  2. Use v-model attribute
  3. Use the sync modifier
  4. Use Vuex

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

like image 87
SanBen Avatar answered Sep 29 '22 16:09

SanBen


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's class 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's title.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.

like image 29
acdcjunior Avatar answered Sep 29 '22 17:09

acdcjunior