How vue use getter setter on v-model? - vue.js

<div id="app">
<input v-model="msg"/>
<p>{{ msg }}</p>
</div>
<script>
class A{
}
A.a = 1
new Vue({
el: '#app',
data: {
},
computed: {
msg: {
cache: false,
set: function(val){
A.a = val
},
get: function(){
return A.a
}
}
}
})
</script>
run on jsfiddle
How vue use getter setter on v-model? I tried use getter and setter on v-model, but it didn't work.

Your getters and setters are fine as is. (They're not strictly necessary in this example, since they're not doing anything to modify the user input, but I assume that's a simplification for the purposes of your question.)
There are two separate issues with your code:
the input field is outside the Vue root node, so the framework can't see it. [You corrected this in a late edit to the question.]
You're defining your data (A.a) outside of Vue, so the framework doesn't know to watch it for changes.
For the framework to be reactive to changes you must put the variable A in the data block of the component (and, if you really need an external variable, copy the updated value into it using the setter function).
new Vue({
el: '#app',
data: {
A: { a: 1 } // <-- your external variable, moved to where Vue can see it
},
computed: {
msg: {
set: function(val) {
this.A.a = val;
// If necessary, also copy val into an external variable here
},
get: function() {
return this.A.a
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<input v-model="msg" />
<p>{{ msg }}</p>
</div>

First of all, your input has to be inside the #app element. Yours is currently not even being watched by Vue instance.
<div id="app">
<input v-model="msg"/>
<p>{{ msg }}</p>
</div>
Also, your A.a = 1 doesn't do anything. If you console.log A's value you won't see a anywhere. Instantiate A and add a variable in it's constructor:
class A {
constructor(a) { this.a = a}
}
let myA = new A(0)
with Vue instance like this it will work:
new Vue({
el: '#app',
data: {
a: myA.a = 1
},
computed: {
msg: {
set: function(val) {
this.a = val
},
get: function() {
return this.a
}
}
}
})
However, I'd move class instantiation to data:
data() {
return {
a: new A(1).a
}
},
If you keep a outside of data your setter will work and update the value, but your getter will not since variables outside of Vue instance aren't being observed.

The code to implement a model in vue is simple as:
var v1 = new Vue({
el:'#vue1',
data:{
msg:'demo'
}
});
And the html as:
<div id='vue1'>
<input type='text' v-model='msg' />
<p>
{{msg}}
</p>
</div>
The first problem is the scope. Since in your Vue instance you are providing the element id as #app, all the vue related markup should be inside an element with id app, in your case the div.
Second, the way you save the data, once you use v-model directive, it directly observes the changes in your model and make changes to the dom accordingly. You do not need the getter and setter methods.
Lastly, what was the code about the class A??
Please look into the the javascript manuals because it is well outside the scope of this question to explain all of that part in detail.
Here is the updated fiddle

Related

Add new key for empty data object not working in Vuejs

I have question related to data of Vue.
I created data with an empty object.
data(){
return {
myObj: {}
}
}
and function like this:
methods: {
changeMyObj() {
this.myObj.newKey = 'aaaa';
}
}
Then I show it on template by click
<a #click="changeMyObj">Click change</a>
{{myObj.newKey}}
With this click, the nested key is not rendered on template. How can I resolve this issue?
Note: I do not meet this issue with Vuex or state of React.
This happens because of vue.js reactivity. In fact, here you are modifying a value that was not declared when the component mounted and Vue cannot track the changes.
You can update values by using the Vue.set method which allows Vue to track the data.
Vue.set(this.myObj, "myKey", "Hello world")
You can also make a deep copy of the object instead of just adding the key.
This can be done using the spread operator.
For example
this.myObj = {...this.myObj, myKey: "Hello world"}
Here is a small example using the two versions
new Vue({
el: "#app",
data: () => ({
myObj: {}
}),
methods: {
addKey(){
this.myObj = {...this.myObj, myKey: "Hello world foo"}
},
addKey2(){
Vue.set(this.myObj, "myKey", "Hello world bar")
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{ myObj.myKey }}
<button #click="addKey">Add key</button>
<button #click="addKey2">Add key 2</button>
</div>
I found the solution for this.
this.myObj = {...this.myObj, newKey:'aaaa'}
I do not think it is solution while it has no difference with:
this.myObj['newKey'] = 'aaaa';
or
this.myObj.newKey = 'aaaa';
If someone can explain why please let me know. Thanks
Correct way to assign a new property in an existing object is Vue.set(this.myObj, 'newKey', 'aaaa') to make it reactive instead of this.myObj.newKey = 'aaaa'
Demo :
new Vue({
el: '#app',
data: {
myObj: {}
},
methods: {
changeMyObj() {
Vue.set(this.myObj, 'newKey', 'aaaa')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="changeMyObj">Click change</button>
{{ myObj.newKey }}
</div>

Vue: How to use an object in component data?

In some component, I have this:
export default {
data() {
return {
a: 'a',
b: {}
};
}
}
Now, if I do this.a = 'aa'; somewhere, and I use this.a in a computed function, then everything happily updates reactively. If, however, I do this.b.key = 'bb'; ... then nothing happens. That is, this.b is not updated reactively, which makes us sad.
After some testing, it seems that I can trigger reactive updates to this.b, but only by assigning this.b = completelyNewObject; ... which is awkward.
Furthermore, in this case, this.b is not iterable, which makes using it in a computed function rather difficult.
So... what am I doing wrong here?
This is because b.key does not exist in the initial state of the data object, so Vue was not able to bind to it. The simple solution would be to add a key property to your initial object. You can also use Vue.set to assign the value.
new Vue({
el: "#app",
data(){
return {
a: {},
b: {
key: ''
},
c: {}
}
},
methods: {
changeA(){
this.a.key = Math.random();
},
changeB(){
this.b.key = Math.random();
},
changeC(){
Vue.set(this.c, 'key', Math.random());
}
}
});
body {
font-family: sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p><button #click="changeA">Change me!</button> <strong>a.key:</strong> {{ a.key }}</p>
<p><button #click="changeB">Change me!</button> <strong>b.key:</strong> {{ b.key }}</p>
<p><button #click="changeC">Change me!</button> <strong>c.key:</strong> {{ c.key }}</p>
</div>
Notice how both b and c are reactive and update instantaneously, while a is not (it does update if you change b or c thanks to Vue re-rendering the app/component).
Well this is a limitation in Vue 2.x, and the possible solutions are using Vue.set or this.$set or Object.assign methods for example
this.$set(this.b, 'key', 'value to assign')
OR
Vue.set(this.b, 'key', 'value to assign')
OR
this.b= Object.assign({}, this.b, { key: 'value to assign' })
Note: this issue is already resolved in Vue 3 but mostly people are still on Vue 2x :)

How to clone props object and make it non-reactive [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 12 days ago.
I have some form data which I share with children components through props. Now I want to clone the prop object and make it non-reactive. In my case I want the user to be able to modify the props value without actually changing the cloned value. The cloned value should only be there to show the user what the form was when editing. Below code shows this:
<template>
<div>
<div v-if="computedFormData">
original prop title: {{orgData.title}}
new title:
<input type="text" v-model="formData.title"/>
//changing data here will also change orgData.title
</div>
</div>
</template>
<script>
export default {
props: ['formData'],
data() {
return {
orgData: [],
}
},
computed: {
computedFormData: function () {
this.orgData = this.formData;
return this.orgData;
},
},
methods: {
},
}
</script>
I have tried with Object.freeze(testData); but it doesnt work, both testData and orgData are reactive. Note also that using mounted or created property does not render orgData so I'm forced to use the computed property.
Try copying the prop values with Object.assign. No more issue with reactivity since the new, assigned values are just the copy instead of the reference to the source.
If your data object is a lot more complex, I'd recommend deepmerge in place of Object.assign.
Vue.component('FormData', {
template: `
<div>
<div v-if="testData">
<p>Original prop title: <strong>{{orgData.title}}</strong></p>
<p>Cloned prop title:</p>
<input type="text" v-model="testData.title" />
</div>
</div>
`,
props: ['orgData'],
data() {
return {
testData: Object.assign({}, this.orgData)
}
}
});
const vm = new Vue({
el: '#app',
data() {
return {
dummyForm: {
title: 'Some title'
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form-data :org-data="dummyForm"></form-data>
</div>
Not entirely sure why but using Object.assign on a computed property did not work for me. I solved it by using a watch property for the props value:
watch:{
formData(){
this.orgData = Object.assign({}, this.formData)
}
},
Object.assign is merely a shallow copy. If you have a copy consists that of only primitive data types (string, number, bigint, boolean, undefined, symbol, and null) it's ok. It to remove its reactivity. But, if you have a copy that has reference types you can’t shallow clone it to remove its reactivity.
For depping clone you can use the JSON.parse(JSON.stringify()) pattern. But keep in mind that is going to work if your data consists of supported JSON data types.
props: ['orgData'],
data() {
return {
cloneOrgData: JSON.parse(JSON.stringify(this.orgData))
}
}

Vue component communication

I'm looking for a concise example of two Vue components. The first component should contain a text input or textarea. The second component displays a character counter. I would like the first component to emit change events, and the second component should listen for those events and display its computed values (character count). I'm new to Vue and trying to wrap my head around the best way to implement this functionality. It seems rather straightforward in pure JavaScript but doing it the Vue way is not as clear to me. Thanks.
Here is how I'd do it in JavaScript:
Here's the textarea:
<textarea id="pagetext" name="pagetext"
onChange="characterCount();"
onKeyup="characterCount();">Type here</textarea>
Here's the JavaScript:
function characterCount()
{
var characters=document.myForm.pagetext.value.length;
document.getElementById('charcounter').innerHTML=characters+"";
}
My concern with Vue is passing the entire value around... for performance reasons this seems less than ideal. I may want my text editing Vue component to self-contain the value and emit the stats, ie the value for character count which would then be observed by a text stats component.
You can create a "Model" for value of textarea and provide this model to second component by using following way https://v2.vuejs.org/v2/guide/components-props.html
I've written up a snippet with four examples: your original, a simple Vue app (no components) that does the same thing, and two apps with two components that are coordinated by the parent.
The simple Vue app is actually more concise than the pure JavaScript app, and I think it shows off the reason for having a framework: your view doesn't act as a store for your program data, from which you have to pull it out.
In the final example, the parent still owns pageText, but passes it down to the my-textarea component. I like to hide the emitting behind the abstraction of a settable computed, so that the element can use v-model. Any changes are emitted up to the parent, which changes pageText, which propagates back down to the component.
I think your performance concerns fall into the realm of premature optimization, but it is possible not to use the text content as data at all, and only be concerned with the length. The fourth example does that. emitLength could have used event.target.value.length, but I wanted to use it in the mounted to initialize the length properly, so I used a ref.
function characterCount() {
var characters = document.myForm.pagetext.value.length;
document.getElementById('charcounter').innerHTML = characters + "";
}
new Vue({
el: '#app',
data: {
pageText: 'Type here'
}
});
new Vue({
el: '#app2',
data: {
pageText: 'Type here'
},
components: {
myTextarea: {
props: ['value'],
template: '<textarea name="pagetext" v-model="proxyValue"></textarea>',
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
}
},
textLength: {
props: ['value'],
template: '<div>{{value}}</div>'
}
}
});
new Vue({
el: '#app3',
data: {
textLength: null
},
components: {
myTextarea: {
template: '<textarea ref="ta" name="pagetext" #input="emitLength">Type here</textarea>',
methods: {
emitLength() {
this.$emit('change', this.$refs.ta.value.length);
}
},
mounted() {
this.emitLength();
}
},
textLength: {
props: ['value'],
template: '<div>{{value}}</div>'
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<form name="myForm">
<textarea id="pagetext" name="pagetext" onChange="characterCount();" onKeyup="characterCount();">Type here</textarea>
</form>
<div id="charcounter"></div>
<div id="app">
<h1>Vue (simple)</h1>
<form>
<textarea name="pagetext" v-model="pageText"></textarea>
</form>
<div>{{pageText.length}}</div>
</div>
<div id="app2">
<h1>Vue (with components)</h1>
<form>
<my-textarea v-model="pageText"></my-textarea>
</form>
<text-length :value="pageText.length"></text-length>
</div>
<div id="app3">
<h1>Vue emitting stats</h1>
<form>
<my-textarea #change="(v) => textLength=v"></my-textarea>
</form>
<text-length :value="textLength"></text-length>
</div>

Vue v-once equivalent

Is there a way to tell Vue to call a method only once when used as an expression?
Here's my code:
<div v-for="i in a.b.c.items">
<div :id="foo(i.value)"></div>
</div>
The way it is now, the foo() method will be executed any time anything on the model changes, not only items. Is there something in Vue that I can tell to evaluate this only once?
like this: <div :id.only-once="foo(i.value)"
Unfortunately that's only possible for certain events, e.g. in this question here. What you may want to consider instead is a computed property where you compute all of these values and return the array. This resulting array will be cached by Vue and will not be reevaluated until your items array is modified (and modified in such a way that Vue will detect the change).
An example:
Vue Setup
<script>
new Vue({
el: '. . .',
data: {
a: {b: {c: {items: [. . .]}}}
},
methods: {
foo: function(val) {
. . .
}
},
computed: {
itemsAfterFoo: function() {
var this_vue_instance = this;
var computed_items = [];
this_vue_instance.items.forEach(function(item) {
computed_items.push(this_vue_instance.foo(item.value));
});
return computed_items;
}
}
});
</script>
Template
<div v-for="(i, index) in a.b.c.items">
<div :id="itemsAfterFoo[index]"></div>
</div>
Or something to that effect.
More information on computed properties here: https://v2.vuejs.org/v2/guide/computed.html