Set data field from getter and add extra computed field - vue.js

I wanted to set fields inside data using getters:
export default {
data () {
return {
medications: [],
}
},
computed: {
...mapGetters([
'allMedications',
'getResidentsById',
]),
I wanted to set medications = allMedications, I know that we can user {{allMedications}} but my problem is suppose I have :
medications {
name: '',
resident: '', this contains id
.......
}
Now I wanted to call getResidentsById and set an extra field on medications as :
medications {
name: '',
resident: '', this contains id
residentName:'' add an extra computed field
.......
}
I have done this way :
watch: {
allMedications() {
// this.medications = this.allMedications
const medicationArray = this.allMedications
this.medications = medicationArray.map(medication =>
({
...medication,
residentName: this.getResidentName(medication.resident)
})
);
},
},
method: {
getResidentName(id) {
const resident = this.getResidentsById(id)
return resident && resident.fullName
},
}
But this seems problem because only when there is change in the allMedications then method on watch gets active and residentName is set.

In situations like this you'll want the watcher to be run as soon as the component is created. You could move the logic within a method, and then call it from both the watcher and the created hook, but there is a simpler way.
You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly as soon as the computed property is resolved.
watch: {
allMedications: {
handler: function (val) {
this.medications = val.map(medication => ({
...medication,
residentName: this.getResidentName(medication.resident)
});
},
immediate: true
}
}

Related

How to use/evoke to set in computed through methods?

I am trying to update data property through computed property and found that it is impossible to set the value but I can use get/set to assign value in data property. Please see my example first.
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle: {
get () {
return this.title
},
set () {
console.log('how can I come to this line?')
this.title = 'update title example'
}
},
isTitle() {
this.color = 'red'
return 'update title example'
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
this.color = 'red'
}
},
example looks little bit weird but what I wanted to ask is..
when getAccessToTitle() is called through mounted, I assume, isTitle's set() should update the title in data property isn't it? I am not sure how can I use set in computed property when I call isTitle through methods but not template(I saw many examples that use template to call computed like https://vuejs.org/guide/essentials/computed.html#writable-computed but it is not what I am looking for!)
Thank you
this is what I wanted to do originally. update color in data and return title in isTitle. Tt works but was told that it is bad way to use computed so I added get/set
data () {
return {
title: '',
color: null
}
},
computed: {
isTitle() {
this.color = 'red' <---
return 'update title example' <---
}
},
mounted () {
this.getAccessToTitle()
},
methods: {
getAccessToTitle () {
if (isTitle) {
isColor(this.color)
}
},
isColor(val) {
// do something...
}

How to create getters and setters for all sub-properties of a Vuex state property efficiently?

I couldn't find the answer anywhere.
Let's say we have Vuex store with the following data:
Vuex store
state: {
dialogs: {
dialogName1: {
value: false,
data: {
fileName: '',
isValid: false,
error: '',
... 10 more properties
}
},
dialogName2: {
value: false,
data: {
type: '',
isValid: false,
error: '',
... 10 more properties
}
}
}
}
Dialogs.vue
<div v-if="dialogName1Value">
<input
v-model="dialogName1DataFileName"
:error="dialogName1DataIsValid"
:error-text="dialogName1DataError"
>
<v-btn #click="dialogName1Value = false">
close dialog
</v-btn>
</div>
<!-- the other dialogs here -->
Question
Let's say we need to modify some of these properties in Dialogs.vue.
What's the best practices for creating a getter and setter for every dialog property efficiently, without having to do it all manually like this:
computed: {
dialogName1Value: {
get () {
return this.$store.state.dialogs.dialogName1.value
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.value', value: value })
}
},
dialogName1DataFileName: {
get () {
return this.$store.state.dialogs.dialogName1.data.fileName
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.fileName', value: value })
}
},
dialogName1DataIsValid: {
get () {
return this.$store.state.dialogs.dialogName1.data.isValid
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.isValid', value: value })
}
},
dialogName1DataIsError: {
get () {
return this.$store.state.dialogs.dialogName1.data.error
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.error', value: value })
}
},
... 10 more properties
And this is only 4 properties...
I suppose I could generate those computed properties programmatically in created(), but is that really the proper way to do it?
Are there obvious, commonly known solutions for this issue that I'm not aware of?
getters can be made to take a parameter as an argument - this can be the 'part' of the underlying state you want to return. This is known as Method-style access. For example:
getFilename: (state) => (dialogName) => {
return state.dialogs[dialogName].data.fileName
}
You can then call this getter as:
store.getters.getFilename('dialogName1')
Note that method style access doesn't provide the 'computed property' style caching that you get with property-style access.
For setting those things in only one central function you can use something like this:
<input
:value="dialogName1DataFileName"
#input="update_inputs($event, 'fileName')">
// ...
methods:{
update_inputs($event, whichProperty){
this.$store.commit("SET_PROPERTIES", {newVal: $event.target.value, which:"whichProperty"})
}
}
mutation handler:
// ..
mutations:{
SET_PROPERTIES(state, payload){
state.dialogName1.data[payload.which] = payload.newVal
}
}
Let me explain more what we done above. First we change to v-model type to :value and #input base. Basically you can think, :value is getter and #input is setter for that property. Then we didn't commit in first place, we calling update_inputs function to commit because we should determine which inner property we will commit, so then we did send this data as a method parameter (for example above code is 'fileName') then, we commit this changes with new value of data and info for which property will change. You can make this logic into your whole code blocks and it will solved your problem.
And one more, if you want to learn more about this article will help you more:
https://pekcan.dev/v-model-using-vuex/

Getting documents with ID from firstore collection

While using Firestore, vuefire, vue-tables-2, I stuck getting document's id.
My data structure is as below.
Here is my code.
<v-client-table :columns="columns" :data="devices" :options="options" :theme="theme" id="dataTable">
import { ClientTable, Event } from 'vue-tables-2'
import { firebase, db } from '../../firebase-configured'
export default {
name: 'Devices',
components: {
ClientTable,
Event
},
data: function() {
return {
devices: [],
columns: ['model', 'id', 'scanTime', 'isStolen'],
options: {
headings: {
model: 'Model',
id: 'Serial No',
scanTime: 'Scan Time',
isStolen: 'Stolen YN'
},
templates: {
id: function(h, row, index) {
return index + ':' + row.id // <<- row.id is undefined
},
isStolen: (h, row, index) => {
return row.isStolen ? 'Y': ''
}
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll'
}
},
useVuex: false,
theme: 'bootstrap4',
template: 'default'
}
},
firestore: {
devices: db.collection('devices')
},
};
My expectation is devices should id property as vuefire docs.
But array this.devices didn't have id field even if I check it exist it console.
Basically, every document already has id attribute, but it's non-enumerable
Any document bound by Vuexfire will retain it's id in the database as
a non-enumerable, read-only property. This makes it easier to write
changes and allows you to only copy the data using the spread operator
or Object.assign.
You can access id directly using device.id. But when passing to vue-tables-2、devices is copied and lost id non-enumerable attribute.
I think you can workaround using computed property
computed: {
devicesWithId() {
if (!this.devices) {
return []
}
return this.devices.map(device => {
...device,
id: device.id
})
}
}
Then, please try using devicesWithId in vue-tables-2 instead.

Binding an object from checkboxes

I need to bind an object from checkboxes, and in this example, a checkbox is its own component:
<input type="checkbox" :value="option.id" v-model="computedChecked">
Here's my data and computed:
data() {
return {
id: 1,
title: 'test title',
checked: {
'users': {
},
},
}
},
computed: {
computedChecked: {
get () {
return this.checked['users'][what here ??];
},
set (value) {
this.checked['users'][value] = {
'id': this.id,
'title': this.title,
}
}
},
....
The above example is a little rough, but it should show you the idea of what I am trying to achieve:
Check checkbox, assign an object to its binding.
Uncheck and binding is gone.
I can't seem to get the binding to worth though.
I assume you want computedChecked to act like an Array, because if it is a Boolean set, it will receive true / false on check / uncheck of the checkbox, and it should be easy to handle the change.
When v-model of a checkbox input is an array, Vue.js expects the array values to stay in sync with the checked status, and on check / uncheck it will assign a fresh array copy of the current checked values, iff:
The current model array contains the target value, and it's unchecked in the event
The current model array does not contain the target value, and it's checked in the event
So in order for your example to work, you need to set up your setter so that every time the check status changes, we can get the latest state from the getter.
Here's a reference implementation:
export default {
name: 'CheckBoxExample',
data () {
return {
id: 1,
title: 'test title',
checked: {
users: {}
}
}
},
computed: {
computedChecked: {
get () {
return Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
},
set (value) {
let current = Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
// calculate the difference
let toAdd = []
let toRemove = []
for (let name of value) {
if (current.indexOf(name) < 0) {
toAdd.push(name)
}
}
for (let name of current) {
if (value.indexOf(name) < 0) {
toRemove.push(name)
}
}
for (let name of toRemove) {
var obj = Object.assign({}, this.checked.users)
delete obj[name]
// we need to update users otherwise the getter won't react on the change
this.checked.users = obj
}
for (let name of toAdd) {
// update the users so that getter will react on the change
this.checked.users = Object.assign({}, this.checked.users, {
[name]: {
'id': this.id,
'title': this.title
}
})
}
console.log('current', current, 'value', value, 'add', toAdd, 'remove', toRemove, 'model', this.checked.users)
}
}
}
}

vue.js $watch array of objects

mounted: function() {
this.$watch('things', function(){console.log('a thing changed')}, true);
}
things is an array of objects [{foo:1}, {foo:2}]
$watch detects when an object is added or removed, but not when values on an object are changed. How can I do that?
You should pass an object instead of boolean as options, so:
mounted: function () {
this.$watch('things', function () {
console.log('a thing changed')
}, {deep:true})
}
Or you could set the watcher into the vue instance like this:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
console.log('a thing changed')
},
deep: true
}
},
...
})
[demo]
There is a more simple way to watch an Array's items without having deep-watch: using computed values
{
el: "#app",
data () {
return {
list: [{a: 0}],
calls: 0,
changes: 0,
}
},
computed: {
copy () { return this.list.slice() },
},
watch: {
copy (a, b) {
this.calls ++
if (a.length !== b.length) return this.onChange()
for (let i=0; i<a.length; i++) {
if (a[i] !== b[i]) return this.onChange()
}
}
},
methods: {
onChange () {
console.log('change')
this.changes ++
},
addItem () { this.list.push({a: 0}) },
incrItem (i) { this.list[i].a ++ },
removeItem(i) { this.list.splice(i, 1) }
}
}
https://jsfiddle.net/aurelienlt89/x2kca57e/15/
The idea is to build a computed value copy that has exactly what we want to check. Computed values are magic and only put watchers on the properties that were actually read (here, the items of list read in list.slice()). The checks in the copy watcher are actually almost useless (except weird corner cases maybe) because computed values are already extremely precise.
If someone needs to get an item that was changed inside the array, please, check it:
JSFiddle Example
The post example code:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
var vm = this;
val.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
var diff = p[prop] !== vm.clonethings[idx][prop];
if(diff) {
p.changed = true;
}
})
});
},
deep: true
}
},
...
})
You can watch each element in an array or dictionary for change independently with $watch('arr.0', () => {}) or $watch('dict.keyName', () => {})
from https://v2.vuejs.org/v2/api/#vm-watch:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
However, you can iterate the dict/array and $watch each item independently. ie. $watch('foo.bar') - this watches changes in the property 'bar' of the object 'foo'.
In this example, we watch all items in arr_of_numbers, also 'foo' properties of all items in arr_of_objects:
mounted() {
this.arr_of_numbers.forEach( (index, val) => {
this.$watch(['arr_of_numbers', index].join('.'), (newVal, oldVal) => {
console.info("arr_of_numbers", newVal, oldVal);
});
});
for (let index in this.arr_of_objects) {
this.$watch(['arr_of_objects', index, 'foo'].join('.'), (newVal, oldVal) => {
console.info("arr_of_objects", this.arr_of_objects[index], newVal, oldVal);
});
}
},
data() {
return {
arr_of_numbers: [0, 1, 2, 3],
arr_of_objects: [{foo: 'foo'}, {foo:'bar'}]
}
}
If your intention is to render and array and watch for changes on rendered items, you can do this:
Create new Component:
const template = `<div hidden></div>`
export default {
template,
props: ['onChangeOf'],
emits: ['do'],
watch: {
onChangeOf: {
handler(changedItem) {
console.log('works')
this.$emit('do', changedItem)
},
deep: true
}
},
}
Register that component:
Vue.component('watcher', watcher)
Use it inside of your foreach rendering:
<tr v-for="food in $store.foods" :key="food.id">
<watcher :onChangeOf="food" #do="(change) => food.name = 'It works!!!'"></watcher>
</tr>