[Vuejs]-Vue mutate prop binded by v-bind with sync modifier

0πŸ‘

βœ…

I found two problems with your example that might throw this off.

  1. 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.

  2. 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>

0πŸ‘

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>

Leave a comment