I am trying to set a computed property as prop to a child component in Vue.js.
This is an excerpt of the component code (Here's the full (not) working example):
<div id="app">
<show-me :title="aTitle"></show-me>
</div>
const ShowMe = {
data() {
return {
_title: null
}
},
computed: {
title: {
set: function(val) {
this._title = val;
},
get: function() {
return this._title;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
When running the example the component's title value in the template is undefined. It's pretty simple and straightforward and I don't get it why it's not working.
_ prefixed properties are reserved for Vue's internal properties. They are not available for direct binding (but you can bind to them as
$data._message) - Evan You (Vue creator)
You can't use _ prefixed values cuz it get treated as internal properties in vue system
Reference - https://github.com/vuejs/vue/issues/2098
Most proper explanation available at Docs too -
Properties that start with _ or $ will not be proxied on the Vue
instance because they may conflict with Vue’s internal properties and
API methods. You will have to access them as vm.$data._property
https://v2.vuejs.org/v2/api/#data
So In your case you will do replace this._title with this.$data._title
const ShowMe = {
data() {
return {
_title: null
}
},
computed: {
title: {
set: function(val) {
this.$data._title = val;
},
get: function() {
return this.$data._title;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
const ShowMe = {
props:{
title:{
type:String,
required:true
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
or you can use simply as follows
const ShowMe = {
props:['title'],
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
This code works. It will be more consistent practice to avoid using this.$data, though the other answer is correct in that you can certainly use it. It is better to avoid the underscores altogether and find a better naming convention. The naming in this example also isn't best practice.
const ShowMe = {
data() {
return {
cTitle: null
}
},
computed: {
title: {
set: function(val) {
this.cTitle = val;
},
get: function() {
return this.cTitle;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
This code will allow you to initialize the initial value and then reactively change the dataTitle field inside the ShowMe component.
If you want to change a property from an external component, then you should do without the dataTitle field and use only props.
const ShowMe = {
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
dataTitle: this.title
}
},
template: `
<div>
should display title: {{dataTitle}} <br/>
but instead typeof title: {{typeof dataTitle}}
</div>`
};
You may also need the ability to change the property both inside ShowMe and from an external component. Then you can use the following code:
JS
const ShowMe = {
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
dataTitle: this.title
}
},
watch: {
title (newValue, oldValue) {
if (newValue !== oldValue) {
this.dataTitle = newValue;
}
},
dataTitle (newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('changeTitle', newValue);
}
}
},
template: `
<div>
should display title: {{dataTitle}} <br/>
but instead typeof title: {{typeof dataTitle}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
HTML
<div id="app">
<show-me :title="aTitle" #changeTitle="aTitle = $event"></show-me>
</div>
Related
I'm using the Vue Currency Input to an input on the app that I'm working on, I'm not sure why I have this weird issue when the old prop comes back even thought no change was fired. The behaviour is: I edit the price and save, but when I click to be editable again the old price pops up.
This is my code:
<template>
<div>
<input ref="input" :value="val" v-currency="options" allow-negative="false" class="editableValue" #change="change" #keydown.enter.prevent>
</div>
</template>
<script>
import { parse } from "vue-currency-input";
import { UTILS } from "#/helpers/utils.js";
export default {
name: "EditableValue",
data(){
return {
val: this.value
}
},
props: {
currency: {
type: Object
},
value: {
type: Number
}
},
computed: {
options() {
return {
autoDecimalMode: true,
currency: {prefix: this.currency.currency + " "},
distractionFree: false
};
},
},
methods: {
change(){
console.log('chamou')
let newValue = parse(this.$refs.input.value, this.options);
this.val = newValue;
this.$emit('finishEditPrice', newValue)
},
},
watch: {
value(current) {
let inputValue = this.$refs.input.value;
let formattedValue = UTILS.getFormattedPrice(current, this.currency);
if (inputValue != formattedValue) {
this.val = formattedValue;
this.$refs.input.value = formattedValue;
}
},
},
updated(){
// if (inputValue != formattedValue) {
// this.val = formattedValue;
// this.$refs.input.value = formattedValue;
// }
}
};
</script>
The updated() and watch was a trial to change this behaviour but no luck. I'm using this version "vue-currency-input": "^1.22.6". Does anyone have any idea how to solve it? Thanks!
I'm trying to filter the results of a table by using vue-multiselect. I can see the selected values in the VUE dev tools as a part of multiselect component. How do I use these values to be used in filter() function to get the filtered table results.
Below you can see my JS script implementation and Template multiselect implementation as well.
JS Script
export default {
data: () => ({
policies: [],
selectedValues: [],
options: [],
}),
methods: {
filterByStatus: function({ label, value }) {
return this.policies.filter(data => {
let status= data.status.toLowerCase().match(this.selectedValues.toLowerCase());
},
Template
<multiselect
v-model="selectedValues"
:options="options"
:multiple="true"
label="label"
track-by="label"
placeholder="Filter by status"
#select="filterByStatus"
></multiselect>
Your select component is using the prop :multiple="true", this means the bound value selectedValues, with v-model, will return an array of policy objects.
Instead of using a filterByStatus function in the methods component options, create two computed properties.
One that computes an array of the selected policies statuses and another one that computes the filtered array of policies you want to display.
Script:
computed: {
selectedStatuses() {
const statuses = []
for (const { status } of this.selectedValues) {
statuses.push(status.toLowerCase())
}
return statuses
},
filteredPolicies() {
if (this.selectedStatuses.length === 0) {
return this.policies
}
const policies = []
for (const policy of this.policies) {
if (this.selectedStatuses.includes(policy.status.toLowerCase())) {
policies.push(policy)
}
}
return policies
}
}
Template:
<multiselect
v-model="selectedValues"
:options="options"
:multiple="true"
label="label"
track-by="label"
placeholder="Filter by status"
></multiselect>
Example:
Vue.config.productionTip = Vue.config.devtools = false
new Vue({
name: 'App',
components: {
Multiselect: window.VueMultiselect.default
},
data() {
return {
policies: [{
label: 'Policy A',
status: 'enabled'
}, {
label: 'Policy B',
status: 'disabled'
}, {
label: 'Policy C',
status: 'Deprecated'
}],
selectedValues: [],
options: [{
label: 'Enabled',
status: 'enabled'
}, {
label: 'Disabled',
status: 'DISABLED'
}, {
label: 'Deprecated',
status: 'DePrEcAtEd'
}]
}
},
computed: {
selectedStatuses() {
const statuses = []
for (const {
status
} of this.selectedValues) {
statuses.push(status.toLowerCase())
}
return statuses
},
filteredPolicies() {
if (this.selectedStatuses.length === 0) {
return this.policies
}
const policies = []
for (const policy of this.policies) {
if (this.selectedStatuses.includes(policy.status.toLowerCase())) {
policies.push(policy)
}
}
return policies
}
},
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<multiselect v-model="selectedValues" :options="options" :multiple="true" label="label" track-by="label" placeholder="Filter by status"></multiselect>
<pre>Policies: {{ filteredPolicies}}</pre>
</div>
It is better to keep the filter function inside computed.
computed:{
filterByStatus: function ({label, value}) {
return this.policies.filter((data) => {
return data.status && data.status.toLowerCase().includes(this.selectedValues.toLowerCase())
});
}
}
Using the filterByStatus in the template section will render the result in real time.
<div>{{filterByStatus}}</div>
you can use watch on selectedValues when any change or selection :
watch:{
selectedValues: function(value){
this.policies.filter(data => {
let status= data.status.toLowerCase().match(this.selectedValues.toLowerCase());
}
}
This how I did in one of my vue3 projects, where I had multiple dropdowns with multi-items can be selected to filter and with a text input box:
const filterByInput = (item: string) =>
item.toLowerCase().includes(state.searchInput.toLowerCase());
const filteredItems = computed(() => {
let fItems = JSON.parse(JSON.stringify(props.items));
if (state.searchInput) {
fItems = fItems.filter((item) => filterByInput(item.title));
}
if (state.filterByState.length) {
fItems = fItems.filter((item) => state.filterByState.includes(item.state));
}
if (state.filterByType.length) {
fItems = fItems.filter((item) => state.filterByType.includes(item.typeId));
}
if (state.filterByPublishing !== null) {
fItems = fItems.filter((item) => item.published === state.filterByPublishing);
}
return fItems;
// NOTE: other options that you can try (should be placed above `return`)
// const filterMultiSelect = (fItems, selectedItems, key) => {
// // OPT-1: foreach
// const arr = [];
// fItems.forEach((item) => {
// if (selectedItems.includes(item[key])) {
// arr.push(item);
// }
// });
// return arr;
// // OPT-2: filter
// return fItems.filter((item) => selectedItems.includes(item[key]));
// };
// if (state.filterByState.length) {
// fItems = filterMultiSelect(fItems, state.filterByType, 'typeId');
// }
});
You can find full code in this gist
I have a vue application where I watch an array for changes. This is working fine. But I'm not sure how to get the index of the array item which has changed, as the watch callback only passes in the old/new values.
Demo: http://jsfiddle.net/q3zd4fmv/
Simplified Example:
new Vue({
el: '#demo',
data: {
things: [{foo:1}, {foo:2}]
},
watch: {
things: {
handler: function (val, oldVal) {
alert('a thing changed')
},
deep: true
}
},
methods: {
change: function () {
this.things[0].foo = 5
}
}
})
Unfortunately, not out of the box. Using a combination of argument destructuring and a custom watch function, you can achieve something that should do it. For example;
new Vue({
el: '#demo',
data: {
things: [{foo:1}, {foo:2}]
},
methods: {
change: function (...args) {
let [thing, after, before] = args;
console.log(thing);
}
},
mounted: function(){
this.things.forEach(thing => {
this.$watch(() => thing, this.change.bind(null, thing))
});
}
})
I am pretty new to watch and trying to figure out why my watch isn't triggering when accessing it as an object. I saw this thread, but it isn't clear to me if my problem is the same. Following is my simplified example (full example got more properties and properties with array
<div id="app">
<input type="text" v-model.lazy="userInfo.name"> {{userInfo.name}}
</div>
JS
new Vue({
el: "#app",
data: {
userInfo: {
name: ''
}
},
methods: {
},
watch: {
userInfo : {
name(oldVal, newVal){
console.log(oldVal +" " + newVal)
},
},
deep: true
}
})
Link to the JSFiddle
Change the watcher to something like this:
new Vue({
el: "#app",
data: {
userInfo: {
name: "null"
}
},
methods: {},
watch: {
"userInfo.name": function(oldVal, newVal) {
console.log(oldVal + " " + newVal);
}
}
});
Refer to the documentation for the same here.
Check the last example.
Here is a short example in your case:
new Vue({
el: "#app",
data: {
userInfo: {
name: 'null'
}
},
computed: {
name() {
return this.userInfo.name;
}
},
methods: {
},
watch: {
name(newVal, oldVal) {
alert(newVal);
alert(oldVal);
}
},
})
I am generating an object onclick with an automatically-generated name. The name will be different each time. I then want to change the object's values with an input using v-model. How can I target the object if the name is unknown? Here's what I have so far:
<ul>
<li v-for="type in types" #click="addNew(type)">{{ type }}</li>
</ul>
<form v-if="Object.keys(newFields).length !== 0">
<input type="text" v-model="newFields[0].?????????">
</form>
<script>
new Vue ({
el: '#app',
data: {
types: [
'date',
'number',
'currency',
'text',
],
savedFields: [
],
newFields: [
]
},
methods: {
addNew: function (type) {
const name = `${type}-${Object.keys(this.savedFields).map(key => key === type).length}`;
if (Object.keys(this.newFields).length == 0) {
this.newFields = Object.assign({}, this.newFields, {
[name]: {
'type': type,
'displayLabel': '',
'defaultValue': '',
}
});
}
},
},
});
You can save the name as a reactive data. For e.g., save it in currentName
<script>
new Vue({
el: "#app",
data: {
//...
currentName: null
},
methods: {
addNew: function (type) {
const name = ""; //...
this.currentName = name;
//...
}
}
});
</script>
and for the v-model,
<input type="text" v-model="newFields[0][currentName]">