Reputation: 3226
Currently I am working on a Vue project where I want to have a model class that stores all its initial data in two properties: original
and attributes
. However I am having difficulties with binding properties to an input via v-model
.
Lets say I have the following class:
class Model
{
constructor () {
return new Proxy(this, {
get: (target, property, receiver) => {
console.log('get');
return target[property];
},
set: (target, property, value, receiver) => {
console.log('set');
target[property] = value + 123;
return true;
}
});
}
}
This class will log get
when getting a property and set
when setting a property. Also while setting it will add 123
at the end of the value. The following will be executed:
let model = new Model();
model.name = 'test'; // Logs: 'set'
console.log(model.name); // Logs: 'get' and 'test123'
This works as expected.
The problems start when I bind these properties to an input via v-model
. The following happens:
Actions are executed on:
<input v-model="model.name" />
...
data () {
return {
model: new Model
};
}
On the first change the property name
will be added to the model
object. No log at all. No added 123
at the end of the value. The model value has changed to { model: 't' }
.
Second change it will log set
and it will add 123
. The model value has changed to { model: 'te123' }
.
Logging the arguments of of the set
call during the second change:
Model {__ob__: Observer}, "name", "te", Proxy {__ob__: Observer}
Anyone had this problem before or knows what is going on here?
After answer of @sphinx
Thank you for your answer! It works!
To make things more complicated I want to have the properties that are in a certain fillable
array to be set in a property called attributes
. However the same behaviour returns.
class Model
{
constructor () {
this.attributes = {};
this.fillable = ['name'];
return new Proxy(this, {
get: (target, property, receiver) => {
console.log('get', property);
if (target.fillable.indexOf(property) > -1) { // Check if in fillable
if (!(property in target.attributes)) {
Vue.set(target.attributes, property, null);
}
return target.attributes[property];
}
return target[property];
},
set: (target, property, value, receiver) => {
console.log('set', property, value);
if (target.fillable.indexOf(property) > -1) {
Vue.set(target.attributes, property, value + 123);
} else {
target[property] = value
}
return true;
}
});
}
}
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
test: new Model
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<span>{{test.name}}</span>
<input v-model="test.name" type="text">
</div>
During the first change the name
property is set directly on the model
object. But the set
method is not called.
During the second change the name
property is changing and the set
method is called.
Any suggestions on why this happens?
Upvotes: 1
Views: 1390
Reputation: 10729
In your exammple, the getter will create one property, but it is not reactivity (check Vue Guide: Reactivity In Depth).
So Vue can't track the changes.
The solution is easy as above guide introduces, uses Vue.set
or vm.$set
to make sure your object is reactivity.
Like below demo:
class Model
{
constructor () {
return new Proxy(this, {
get: (target, property, receiver) => {
console.log('get', property);
if(!(property in target)) Vue.set(target, property, null)
return target[property]
},
set: (target, property, value, receiver) => {
console.log('set', property, value);
Vue.set(target, property, value + 123)
return true;
}
});
}
}
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
test: new Model
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<span>{{test.name}}</span>
<input v-model="test.name" type="text">
</div>
Upvotes: 2