Vue js passing data owned by component to methods owned by component - vue.js

Trying to pass different arrays that are within "data" of a vue component to methods within the same vue component. I am trying to do this to have different variables be affected by the same method without having to have an individual method for each variable.
Tried having the variable be passed as a parameter to the method and it did not respond as expected. It seemed as though the variable being passed was a copy of the data variable rather than the actual value. I want to do this to allow the data to be modified in a method before being displayed on the page
Example data:
data () {
return {
a: [],
b: []
}
}
How I'd like watchers to work:
a {
this.method(this.a)
}
b {
this.method(this.b)
}
Methods:
method(value) {
value.add(1)
}
Result currently would be a and b not be changed

UPDATE:
Based on your comments it looks like what you are trying to do is reuse a method in your component to mutate two different arrays in your viewmodel.
You can create a method that gets a collection, the value and an action that you want to run.
NOTE: This might end up making your html bindings a bit messy, as you would need to have something like:
<button type="button" #click="this.mutate(a, 'a', (c, v) => c.push(v))">Add to a</button>
Here's a sample snippet where a shared method is used to mutate and filter the array. I added wrapper methods to keep the html bindings clean (which ends up with the same number of methods but less repetition).
Vue.component('some-component', {
template: `
<div>
<div>a: {{a}}</div>
<div>b: {{b}}</div>
<button type="button" #click="onAdd(a, 'a')">Add to a</button>
<button type="button" #click="onSplice(a, 1)">Splice a</button>
<br>
<button type="button" #click="onAdd(b, 'b')">Add to b</button>
<button type="button" #click="onSplice(b, 1)">Splice b</button>
</div>
`,
data:() => ({ a: [], b: [] }),
methods: {
mutate(collection, value, callback){
callback(collection, value);
},
onAdd(collection, value) {
this.mutate(collection, value, (c, v) => c.push(v));
},
onSplice(collection, value) {
this.mutate(collection, value, (c, v) => c.splice(0, value));
}
}
})
new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<some-component></some-component>
</div>

Watchers must be functions, you can do:
watch: {
a: function(aNewValue) {
this.method(aNewValue)
}
}
Anyway, if you intend that every time a variable changes, a change in another variable is triggered, you should use a computed property.

Related

Vue.js 2: How to bind to a component method?

I have a VueJS (v2) component with a private array of objects this.private.messagesReceived which I want displayed in a textarea. The array should be converted to a string by a method/function and Vue is blocking all my attempts to bind. Every attempt results in my serialization function (converting the array to a string) only being called once and never again when the data changes.
I feel there must be a way to do this without Vue.set() or some forceUpdate shenanigans.
https://jsfiddle.net/hdme34ca/
Attempt 1: Computed Methods
Here we have the problem that Vue only calls my computed method messagesReceived1 once and never again.
<script>
{
computed: {
messagesReceived1() {
console.log("This is called once and never again even when new messages arrive");
return this.private.messagesReceived.join("\n");
},
...
methods: {
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
<script>
<template>
<textarea rows="10" cols="40" v-model="messagesReceived1"></textarea>
</template
Attempt 2: Binding Methods
Here Vue decides it doesn't like moustaches inside a textarea {{ messagesReceived2() }} and balks. It also doesn't allow messagesReceived2() or messagesReceived2 in v-model.
<script>
{
methods: {
messagesReceived2() {
return this.private.messagesReceived.join("\n");
},
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
</script>
<template>
<textarea rows="10" cols="40">{{ messagesReceived2() }}</textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2()"></textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2"></textarea><!--Nope-->
</template
You can define a data variable and set its value in the function. Then bind variable with textarea, not directly with the function.

Array filter with props doesn't seem to work correctly

According to the document, using filter should be very OK. But mine does't work: (try click Remove One, and then Filter One, and then back on Remove One, and then as you can see Remove works, but Filter does't)
new Vue({
el: '#app',
data() {
return {
answers: ['a', 'b', 'c']
}
},
methods: {
removeOne() {
this.answers.shift();
// console.log(this.answers);
},
filterOne() {
this.answers = this.answers.filter((e) => { return e === 'b'});
console.log(this.answers);
}
},
components: {
'mytest': {
template: `<ul><li v-for="(v, i) in myAnswers" :key="i">{{ v }}</li></ul>`,
props: ['answers'],
data() {
return {
myAnswers: this.answers
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='app'>
<mytest :answers="answers"></mytest>
<input #click="removeOne" type="button" value="Remove One" />
<input #click="filterOne" type="button" value="Filter One" />
</div>
Your problem is the following:
You have two components (#app and mytest)
answers is an array in #app data
You pass a reference to the #app answers array to the mytest component as a prop.
in removeOne you are mutating the parent answers array. It is the same array that mytest sees and therefore the mytest view updates itself to reflect this change
in filterOne you don't mutate the original array. You replace the parent answers array with another one with some elements filtered out. However the mytest component still has a reference to the original answers array that was passed as a prop that has not been modified by anyone, and therefore the view doesn't change.

How vue use getter setter on v-model?

<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

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

Get reference to element in method in Vue.js

How can I get reference to the element that fired the method in Vue.js?
I have HTML like this:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" />
And in my Vue.js viewmodel I have a method:
dataFieldClass: function () {
// Here I need the element and get its ID
// Pseudo code
var elementId = $element.id;
}
I know that it's possible to get the element from event (v-on:click), but this is not an event, it's a simple method returning CSS class for the element according to few conditions of the viewmodel. It should be computable as well, but the problem is the same.
You can get the reference to your element in three ways
1. with Method Event Handlers (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" />
script:
dataFieldClass: function (e) {
const element = e.target;
}
2. with Inline Handlers (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass($event, otherArgument)" />
script:
dataFieldClass: function (e, otherArgument) {
const element = e.target;
}
3. with Refs (doc)
template:
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" ref="el"/>
script:
dataFieldClass: function () {
const element = this.$refs.el;
}
Maybe you could use ref?
<input type="text" v-model="dataField" v-bind:class="dataFieldClass" ref="el" />
And use it like this:
dataFieldClass: function () {
var elementId = this.$refs.el;
}
See documentation here: https://v2.vuejs.org/v2/api/#ref
What about using the ref pattern. Put ref="someName" in your DOM element, and access it in your method with this.$refs["someName"] (you can pass 'someName' as parameter to your method).
Note that's not a very good pattern except if for some reason you really need the DOM element. Otherwise just pass a relevant parameter to your method.
It's not a good method mainly because it has a major drawback: there is no $refs the first time the vue is rendered (because the element is not present yet). So you should force the vue to render twice.
If you have multiple elements inside a v-for loop, then this.$refs["someName"] becomes an array. You can get it to work with some adaptation, here is an example:
new Vue({
el: '#app',
data() {
return {
fields: [{
name: 'field1',
value: 'value1'
},
{
name: 'field2',
value: 'value2'
}
]
};
},
methods: {
dataFieldClass(index) {
if (!this.$refs.fields) {
// First render, the element is not there yet
return '';
} else {
// Here is the element
console.log(this.$refs.fields[index]);
}
}
},
mounted() {
// Force the instance to render a second time
this.$forceUpdate();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<label v-for="(field, index) in fields">
{{ field.name }}:
<input ref="fields" :value="field.value" v-bind:class="dataFieldClass(index)">
</label>
</div>
You can get the reference from DOM event object. "event.currentTarget" is the property that references the element where the event listener(vuejs method) assigned.
This is standard DOM specification, but you can also use this property in Vuejs.
dataFieldClass: function (event) {
var elementId = event.currentTarget.id;
}
A straightforward solution is to pass a reference to the element in the method to be called.
Here's what worked for me (a pretty basic example to help understand):
new Vue({
el: '#app',
data: {
msg: '',
},
methods: {
// in order to access the HTML element,
// add an argument (namely 'event') in the method definition,
// and access the element's current value by `event.target.value`
updateValue: function(event) {
this.msg = event.target.value;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input :value="msg" #input="updateValue" autofocus>
<br/>
<h2>
>>> {{ msg }}
</h2>
</div>
This seem to work for me, using ref (if element is nested another element)
<div ref="element">
vm.$refs.element
or $el if targeted element is the outermost
<template><div class="targeted-element">
this.$el
You can use refs as mentioned in other answers here.
Remember, refs cannot apply to computed objects. So be careful when using refs