Why doesn't Vue run/update a computed property with a reference to this inside a filter function? - vue.js

I have a select box connected to a Vue computed property. I'm wondering why one of my computed property attempts works and the other doesn't.
<select>
<option v-for="option in filteredItems">{{option.description}}</option>
</select>
filteredItems is a computed property. This code works:
vInstance = new Vue({
...
computed: {
filteredItems: function(){
let someID = this.filterID;
return this.allItems.filter(function(item){
return item.id === someID;
})
}
}
})
This version does not
computed: {
filteredItems: function(){
return this.allItems.filter(function(item){
return item.id === this.filterID;
})
}
}
The two functions are almost identical, other than that the first version sets this.filterID to a different varaible to be used in the filter. Why does that work, and the other doesn't?

It's nothing related with Vue itself. It's how this works in JS. In the second code block, this will probably the window and therefore this.filterID will be undefined. However in the first code example, this will be Vue instance therefore this.filterID will be defined. Take a look at this link to read more about this scope in JS.

Related

Computed function running without to call it

I'm setting an array in my data property through a computed function and it's working. But I wonder how is possible if I don't call it anywhere?
If I try to add a console.log in my function it doesn't print anything, but it's still setting my data, how is that possible?
My data:
data() {
return {
projects: []
};
},
My computed:
computed: {
loadedProjects() {
console.log("Hello there")
this.projects = this.$store.getters.loadedProjects
}
},
I expect that it doesn't run because I'm not calling, and if it is running(I don't know why) to print the console.log before to set my data. Any clarification?
Thanks:)
You're confusing computed props with methods. If you want to have a method like above that sets a data value of your vue instace, you should use a method, not a computed prop:
data() {
return {
projects: []
};
},
methods: {
loadProjects() {
console.log("Hello there")
this.projects = this.$store.getters.loadedProjects
}
}
This would get the value of this.$store.getters.loadedProjects once and assign it to your local projects value. Now since you're using Vuex, you probably want your local reference to stay in sync with updates you do to the store value. This is where computed props come in handy. You actually won't need the projects in data at all. All you need is the computed prop:
computed: {
projects() {
return this.$store.getters.loadedProjects
}
},
Now vue will update your local reference to projects whenever the store updates. Then you can use it just like a normal value in your template. For example
<template>
<div v-for='item in projects' :key='item.uuid'>
{{item.name}}
</div>
</template>
Avoid side effects in your computed properties, e.g. assigning values directly, computed values should always return a value themselves. This could be applying a filter to your existing data e.g.
computed: {
completedProjects() {
return this.$store.getters.loadedProjects.filter(x => x.projectCompleted)
},
projectIds() {
return this.$store.getters.loadedProjects.map(x => x.uuid)
}
}
You get the idea..
More about best practices to bring vuex state to your components here: https://vuex.vuejs.org/guide/state.html
Computed props docs:
https://v2.vuejs.org/v2/guide/computed.html
You should check Vue docs about computed properties and methods
and shouldn't run methods inside computed property getter
https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed.

Vue - instance data is not defined

I have a menu of topics (e.g. "About us", "Support") and I want to be able to tell what topic is active right now. When a topic is clicked it will become the active one. Think of it as a substitute for a router that will set an active route. This is not possible in my case since I'm working in SharePoint 2010... in a content editor. So I made the decision to have an activeTopic variable in my Vue instance.
The Vue instance
new Vue({
el: '#app',
data: {
activeTopic: '',
}
});
This is because the activeTopic has to update another component and show some data based on the active topic. Like a router showing a child route.
The topic component
Vue.component('topic', {
props: ["title"],
methods: {
setActiveTopic: function (title) {
activeTopic = title;
},
isActiveTopic: function (title) {
return activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
The setActiveTopic function works as it should when I click it; it updates the activeTopic. But the isActiveTopic function gives me this error:
Error in render: "ReferenceError: activeTopic is not defined"
What I don't understand is that I can edit the activeTopic variable but I can't make a comparison? I've tried setting a default value but it still says it is undefined.
activeTopic should be assigned to the Vue component by setting and reading this.activeTopic. As is, you have two independent activeTopic variables scoped within each of your component methods.
You'll also want to include activeTopic in the component's data block, rather than on the Vue object itself, since it's specific to the component.
(If there's a reason to put that variable on the Vue object you would either want to pass it down to the component as a prop, or else access it directly as Vue.activeTopic instead of this.activeTopic. I'm honestly not certain whether Vue would treat that last structure as a reactive value within components -- I've never had a reason to try that, and can't think of a situation where that'd be a reasonable architecture.)
Vue.component('topic', {
props: ["title"],
data() { return {
activeTopic: ''
}},
methods: {
setActiveTopic(title) {
this.activeTopic = title;
},
isActiveTopic(title) {
return this.activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
I am new to Vue, and have seen data being defined as an object in several examples.
But apparently, as per the documentation, you have to use data as a function that returns an object.
This is to ensure that all instances will have it's own version of the data object, and is not shared with all instances.
Instead of
data: {
count: 0
}
use
data: function () {
return {
count: 0
}
}
However I still don't know why in one of my component, data as an object worked, and in a child component with it's own data as an object, I got the error count is undefined;
I am assuming, the root element, (defined by new Vue({}) has to be a singleton, and is not intended to have several instances.
And since components, can have instances, the data in that needs to be defined as a function.

Vue wrapper example

There are a good examples of integrating vue with select2.
I have a question.
If we look at this part of the code:
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
})
}
I don't understand, why when we change value of select2, this.value changes too.
I expected a record like:
.on('change', function () {
this.value = $(this.$el).val()
vm.$emit('input', this.value)
})
It behaves that way because of how v-model works. What you are a looking at is a 2-way binding. Where if the value of selected in v-model="selected" changes, the value will be pushed down to the component.
When vm.$emit('input', this.value) is called, it tells the parent to update whatever variable is listening to changes, in this case selected, which it turn gets pushed back to the component such that its value gets changed.
To make it simpler to understand, this is the sequence of events:
select2's value changes
the select2 value change triggers an event emission
the parent receives the event and updates selected
the component's value gets assigned to the new value of selected
the watcher for the value gets triggered and updates select2 with the new value
Good question though.
Caveat: Doing this is understandably poor practice. It will break it sad ways when used with 1-way bindings.
After writing my previous answer, I realized I missed something important: contexts.
On line 5 in the jsfiddle, there is this line:
var vm = this, why?
This is because later on, when doing vm.$emit, this.$emit will not work; the context has been changed.
.on('change', function () {
//this = select2 element
vm.value // == 1
this.value // == 2
vm.$emit("input", this.value);
})
The value on the emit event is not that of the component, but of the select2 element.
While the value has not yet been changed on the component, the new value has been broadcasted to the parent.
Notice how select2 component used in the template:
<select2 :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</select2>
In Vue.js using v-model with components could be simplified to:
<your-component
:value="selected"
#input="value => { selected = value }">
</your-component>
So every time you emit an event from your component the value gets changed at the parent component.
Source

Default select option is not being updated in DOM, even though the v-model attribute is updating

I have a component that builds a select/dropdown menu.
There is an API call that results in both collection and selectedOption getting updated and successfully building the dropdown.
<!-- src/components/systems/SystemEdit.vue -->
<custom-dropdown v-model="system.sensor_type" id="sensor_type" name="sensor_type" :collection="sensorTypeDropdown.collection" :firstOption="sensorTypeDropdown.firstOption" :selectedOption="sensorTypeDropdown.selected"></custom-dropdown>
<!-- src/components/CustomDropdown.vue -->
<template>
<select v-model="selection" required="">
<option v-show="firstOption" value="null">{{firstOption}}</option>
<option v-for="item in collection" :value="item[1]">{{ item[0] }}</option>
</select>
</template>
<script>
export default {
props: ['value', 'firstOption', 'collection', 'selectedOption'],
name: 'customDropdown',
data () {
return {
selection: null
}
},
watch: {
selection: function(sel) {
this.selection = sel
this.$emit('input',sel)
}
},
updated: function() {
if(this.selectedOption) {
this.selection = this.selectedOption
}
}
}
</script>
<style>
</style>
The dropdown output looks like this:
<select required="required" id="sensor_type" name="sensor_type">
<option value="null">Select sensor type...</option>
<option value="sensor1">sensor1</option>
<option value="sensor2">sensor2</option>
<option value="sensor3">sensor3</option>
</select>
If I set the selection: "sensor1" data attribute, then sensor1 is selected, as expected.
Instead, if I update this.selection = this.selectedOption or this.selection = "sensor1", then it does not become the selected option… even though console.log(this.selection) confirms the change. I have tried setting it in the created hook and elsewhere. If I save a change, the dropdown default successfully sets on the hot reload. But that first load where it initializes will not work.
How can I get this.selection = this.selectedOption to properly reflect in the DOM to show that option as selected?
I'm using Vue 2.1.10
Edit 1:
Still no luck, now with this approach:
data () {
return {
selection: this.setSelected()
}
},
methods: {
setSelected: function() {
var sel = null
if(this.selectedOption) {
sel = this.selectedOption
}
console.log(sel)
return sel
}
}
Even though sel outputs "sensor1" and the Vue inspector confirms selection:"sensor1"
Edit 2:
I've narrowed down the problem but cannot yet find a solution.
methods: {
setSelected: function() {
var sel = null
if(this.selectedOption) {
sel = this.selectedOption
}
return sel
}
}
It fails on return sel.
sel is not being treated as a string; I suspect this is a Vue bug.
Works:
return 'sensor1'
Fails:
return sel
I have tried all these workarounds (none of them work):
sel = String(this.selectedOption)
sel = (this.selectedOption).toString()
sel = new String(this.selectedOption)
sel = this.selectedOption+''
sel = 'sensor1'
The only way this will "work" is if I use the string literal, which is useless to me.
I am stumped.
SOLVED
In order for this to work, the selectedOption prop that I am passing to the component must be rendered somewhere in the component's template, even if you have no use for it in the template.
Fails:
<select v-model="selection" required="">
Works:
<select v-model="selection" required="" :data-selected="selectedOption">
That data attribute can be :data-whatever… as long as it is rendered somewhere in the template.
This works, too:
<option v-show="!selectedOption" value="null">{{selectedOption}}</option>
or
<div :id="selectedOption"></div>
I hope this helps someone. I'm going to let Evan You know about it, as it seems to be something that should be fixed or at least noted in the official documentation.
Summary:
Props used in a component's script block must also be rendered somewhere in the component's template, otherwise some unexpected behaviour may occur.
The prop I am using in the script block, selectedOption, needs to be rendered in the template even though it's not being used there.
So this:
<select v-model="selection" required="">
becomes:
<select v-model="selection" required="" :data-selected="selectedOption">
Now the selected item successfully updates in the DOM.
VueJS has a specific configuration for v-model on custom components. In particular the component should have a value prop, which will pick up the v-model from the parent. You then this.$emit('input', newVal) on change like you have configured.
I'm not sure exactly where the breakdown is with your component but here is a working example. You may have just over complicated changing the selected data. If configured correctly you can just emit the change and let the parent handle actually updating what is assigned to v-model. In the example I included some buttons to change the selected var from both the parent and component to illustrate changing the var (parent) or emitting a change (component).

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'