Vue: v-model doesn't work with dynamic components - javascript

For example: <component v-model='foo' :is='boo' ...>.
foo's value stays the same during input.
I'm trying to solve the issue for quite a long time. I've checked lots of questions and threads but none of those helped me.
HTML doesn't work:
<component
:is="field.component"
:key="key"
:name="field.name"
v-for="(field, key) in integration_data"
v-model="field.value"
>
</component>
HTML works fine:
<input
:key="key"
:name="field.name"
v-for="(field, key) in integration_data"
v-model="field.value"
>
Vue controller:
export default {
init: function (init_data) {
return new Vue({
data: {
integration_data: [
{name: 'field_name0', component: 'input', value: ''},
{name: 'field_name0', component: 'input', value: ''},
]
},
});
}
}

You can't use input as a type of component and expect it to be a native input element. :is must name a component (which can contain an input, if you want).
Then you have to understand how v-model works on components:
So for a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
Putting that all together, you can use v-model with :is.
new Vue({
el: '#app',
data: {
integration_data: [{
name: 'one',
component: 'one',
value: 'ok'
}]
},
components: {
one: {
props: ['name', 'value'],
template: '<div>{{name}} and <input v-model="proxyValue"><slot></slot></div>',
computed: {
proxyValue: {
get() { return this.value; },
set(newValue) { this.$emit('input', newValue); }
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<component
:is="field.component"
v-for="(field, key) in integration_data"
:key="key"
:name="field.name"
v-model="field.value"
>
<div>{{field.value}}</div>
</component>
</div>

v-model has nothing to do with what you're possibly trying to do. It looks like you are trying to insert components dynamically. That's exactly what :is does. Now, to pass data to the component, you should use props.
For example:
Your Vue instance:
const vm = new Vue({
el: '#app',
data: {
exampleValue: 'This value will be passed to the example component'
}
})
Register a component:
Vue.component('example-component', {
// declare the props
props: ['value'],
template: '<span>{{ value}}</span>'
})
Then use it like this:
<example-component :value="exampleValue"></example-component>
Or:
<component :is="'example-component'" :value="exampleValue"></component>

Related

Vue array prop in child component not updating

I have a model from a backend where the property that normally contains an array of elements can be nullable. When this happens I'll init it to an empty array. However when doing that it seems to break my ability to update the array in the child component. Sample code can be found below.
Vue.component('Notes', {
template: '<div>{{items}}<ul><li v-for="item in items">{{ item.text }}</li></ul><button #click="add">Add</button></div>',
props: {
items: Array,
},
methods: {
add() {
console.log('added');
this.items.push({ text: "new item" });
}
}
});
new Vue({
el: "#demo",
data() { return { model: { } }},
created() { if(!this.model.items) this.model.items = []; }
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<Notes :items="model.items" />
</div>
If data in the main component is model: { items : [] } everything works fine. But I don't have control over the backend data to guarantee that.
In your Notes component, you declare a model in the data, then, just underneath, you add an items[] if one doesn't exist already. This is not a good practice, and could be the cause of your problems. Vue needs to know about all the properties on objects it's watching. They need to be there when Vue first processes the object, or you need to add them with Vue.set().
You should emit an event to update the prop in the parent component
in child component :
this.$emit('add-item',{
text: "new item"
});
in parent template add a handler for the emitted event :
<Notes :items="model.items" #add-item="AddItem" />
Vue.component('Notes', {
template: '<div>{{items}}<ul><li v-for="item in items">{{ item.text }}</li></ul><button #click="add">Add</button></div>',
props: {
items: Array,
},
methods: {
add() {
console.log('added');
this.$emit('add-item', {
text: "new item"
});
}
}
});
new Vue({
el: "#demo",
data() {
return {
model: {
items: [] //init it here in order to avoid reactivity issues
}
}
},
methods: {
AddItem(item) {
this.model.items.push(item)
}
},
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<Notes :items="model.items" #add-item="AddItem" />
</div>

Passing dynamic boolean props to a VueJS component

How can I attach dynamic properties to a VueJS Component using VuetifyJS?
I have the following VuetifyJS code example that creates a select field element:
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
:items="items"
multiple attach chips>
</v-select>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectField: '',
items: [
'item1',
'item2',
'item3'
],
booleanProperties: [
'multiple',
'attach',
'chips'
]
}
},
})
This creates a functional VuetifyJS select component, however I want to know how to pass the boolean props multiple, attach, chips to the select element as data properties so they do not have to be specified explicitly in the component declaration.
For example: I want to add the props: multiple, attach, chips defined within the data array element booleanProperties while still being able to define the component without having them specified. This way it works dynamically for any prop I pass.
Something similar to the following pseudocode example.
<v-select
v-model="selectField"
:items="items"
v-for="(booleanProperties) as boolProp">
</v-select>
How can I pass/specify the data props: multiple, attach, chips dynamically for the v-select element?
Here is a code example of what I am referring to.
https://codepen.io/deftonez4me/pen/NWRLWKE
You can simply use v-bind without specifying the key/property, and then passing an object into it, i.e. v-bind="additionalProps". As per VueJS v2 documentation on v-bind:
When used without an argument, can be used to bind an object containing attribute name-value pairs.
You can also merge your items binding into the object returned by additionalProps then, for brevity. Example based on your code.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectField: '',
additionalProps: {
items: [
'item1',
'item2',
'item3'
],
multiple: true,
attach: true,
chips: true
}
}
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/2.3.16/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/2.3.16/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
v-bind="additionalProps">
</v-select>
</v-app>
</div>
You can go simply as this:
<div id="app">
<v-app id="inspire" style="padding: 10px; ">
<v-select
v-model="selectField"
:items="items"
:multiple="multiple"
:attach="attach"
:chips="chips">
</v-select>
</v-app>
</div>
Then you can declare the props as before:
props: [
'multiple',
'attach',
'chips'
]
Or a bit more specific like this:
props: {
multiple: {
type: Boolean
},
attach: {
type: Boolean
},
chips: {
type: Boolean
}
}

Short hand to pass boolean props to Vue component

In the Vue docs for components it says:
Including the prop with no value will imply true:
<blog-post favorited></blog-post>
However, when I try it on my component, it doesn't work (related fiddle):
<!-- html -->
<div id="app">
<test-component visible></test-component>
</div>
<template id="template">
<span>open: {{ open }}; visible: {{ visible }}</span>
</template>
<!-- JS -->
const TestComponent = Vue.extend({
template: '#template',
props: ['visible'],
data: function() {
return { 'open': true }
}
});
new Vue({
el: "#app",
components: {
'test-component': TestComponent
}
});
Is this a bug or am I doing something wrong?
I would also expect it to work as it is, but it seems you need to specify the type of field in the props declaration:
props: {
'visible': {
type: Boolean
}
}
This makes it work correctly

how to bind v-model to child component that contains input

I have some components that look like this.
<template>
<q-layout>
<v-input v-model="something" />
</q-layout>
</template>
<script>
import { QLayout } from 'quasar'
import { Input } from 'vedana'
export default {
name: 'index',
components: {
QLayout,
Input
},
data () {
return {
something: ''
}
}
}
this v-input component looks like this:
<template>
<input
:type="type ? type : 'text'"
class="v-input"/>
</template>
<script>
export default {
props: ['type'],
name: 'v-input'
}
</script>
When I enter data into the input something does not bind to whatever is in the value of the input that is inside of v-input.
How do I achieve this?
To enable the use of v-model the inner component must take a value property.
Bind the value to the inner <input> using :value, not v-model (this would mutate the prop coming from the parent). And when the inner <input> is edited, emit an input event for the parent, to update its value (input event will update the variable the parent has on v-model).
Also, if you have a default value for the type prop, declare it in props, not in the template.
Here's how your code should be
<template>
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
<script>
export default {
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
}
</script>
Info about what props can have: Components / Passing data With Props.
Demo below.
Vue.component('v-input', {
template: '#v-input-template',
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
});
new Vue({
el: '#app',
data: {
something: "I'm something"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<p>Parent something: {{ something }}</p>
<hr>
Child: <v-input v-model="something" />
</div>
<template id="v-input-template">
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
<template>
<q-layout>
<v-input :value.sync="something" />
</q-layout>
</template>
<template>
<input
:type="type ? type : 'text'"
v-model="inputVal"
class="v-input"/>
</template>
<script>
export default {
props: ['type', 'value'],
name: 'v-input',
data:function(){
return {
inputVal: ''
};
},
watch:{
value: function(newValue){
this.$emit('update:value', newValue)
}
}
}
</script>
You need to pass your value to the input component using the .sync modifier so the changes will sync back to the parent.

"Property is not defined on the instance" even though it is defined

I wanted to add some own scenarios for new components by forking vue-play.
I'm having problems in more complicated cases of vue-select, particularly Two-Way Value Syncing.
Going into this scenario ends up with warning:
vue.esm.js:571 [Vue warn]: Property or method "syncedVal" is not
defined on the instance but referenced during render.
and no option in the dropdown is preselected either. I'm failing to understand why I keep getting this warning, despite defining syncedVal in component's props.
I've added two files into vue-play/play:
VSelect.vue:
<template>
<v-select v-model="selected" :options="options"></v-select>
</template>
<script>
import Vue from 'vue'
import vSelect from 'vue-select'
Vue.component('v-select', vSelect);
export default {
props: {
options: {
default: function() { return ['one', 'two'] },
type: Array
},
onchangeCallback: {
default: () => () => null
},
// doesn't seem to work:
syncedVal: {
default: 'one',
type: String
}
},
data() {
return {
selected: null
}
}
}
</script>
and VSelect.play.js:
import {play} from '../src/play'
import VSelect from './VSelect.vue'
play(VSelect)
.name('VSelect')
.displayName('VSelect')
.add('default', '<v-select />')
.add('multiple', '<v-select multiple />')
.add('custom options', `<v-select :options="['custom1','custom2']" />`)
.add('custom options with labels', `<v-select :options='[{value: "CA", label: "Canada"}, {value: "UK", label: "United Kingdom"}]' />`)
.add('2-way value sync', `<v-select :value.sync="syncedVal" />`) // doesn't seem to work
please note that the v-select component in VSelect.play.js is VSelect.vue
so there are some mistakes:
.add('multiple', '<v-select multiple />')
VSelect.vue do not have multiple props, so this multiple will not work as you expectd
FIX: add props to your component, and bind it to v-select
.add('2-way value sync', )
you define syncedVal in component's props, BUT you use it on other component (vue-play's component), they have different scope!
FIX: to use vue-play to demo this functionality, you need to write a full component, so you can have data to bind(see below for example code)
VSelect.vue do not implement sync (https://v2.vuejs.org/v2/guide/components.html#sync-Modifier), so nothing will happen here
I make an example from your component and play, I hope this will help you :)
Demo of my vue-play: https://vue-play-select.netlify.com/
Code: https://github.com/iampaul83/vue-play-select
here is how I fix them:
SelectFramework.vue:
<template>
<v-select
v-model="selected"
:options="options"
:multiple="multiple">
</v-select>
</template>
<script>
export default {
name: "SelectFramework",
props: {
options: {
default: () => ["Vue.js", "React", "Angular"],
type: Array
},
value: String, // to support v-model
foo: String, // to support :foo.sync
multiple: false // to support multiple select
},
data() {
return {
selected: this.value,
};
},
watch: {
selected () {
this.$emit('input', this.selected) // update v-model
this.$emit('update:foo', this.selected) // update foo.sync
},
value () {
this.selected = this.value // update from v-model
},
foo () {
this.selected = this.foo // update from foo.sync
}
}
};
</script>
play/index.js:
import { play } from 'vue-play'
import Vue from 'vue'
import vSelect from 'vue-select'
Vue.component('v-select', vSelect)
import SelectFramework from '../src/SelectFramework.vue'
play(SelectFramework)
.name('SelectFramework')
.displayName('Select Framework')
.add('default', '<select-framework />')
.add('multiple', '<select-framework multiple />')
.add('custom options', `<select-framework :options="['custom1','custom2']" />`)
.add('custom options with labels', `<select-framework :options='[{value: "CA", label: "Canada"}, {value: "UK", label: "United Kingdom"}]' />`)
// full component to demo v-model and :foo.sync
.add('v-model', {
data() {
return {
selected: null,
syncedVal: null
}
},
template: `
<div>
<p>selected: {{selected}} </p>
<p>syncedVal: {{syncedVal}} </p>
<select-framework
v-model="selected"
:foo.sync="syncedVal">
</select-framework>
<p>
<button #click="selected = 'Vue.js'">Set selected to Vue.js</button>
<button #click="syncedVal = 'React'">Set syncedVal to React</button>
</p>
</div>
`
})
// .add('2-way value sync', `<select-framework :value.sync="syncedVal" />`) // doesn't seem to work

Categories