When I add dynamic keys in object, the observer in inputs stops working. For example:
<template lang="pug">
input#field(v-model="block[current]")
button(#click="current = 'de'") change
button(#click="addVal") add
</template>
<script>
data() {
return {
current: "en"
block: {}
}
},
methods: {
addVal() {
this.block.de = "adawdawdawd";
}
}
</script>
Now if I type in #fiel, block.de wont work. But if I'm not adding values, it works fine.
Try to use this.$set to update it because you have a reactivity issue:
this.$set(this.block,"de" , "adawdawdawd");
Related
I am trying to use a Vue watcher on a computed object and it is not working at all. It works properly if it's just a string, but not if it's an object. I followed the Vue documentation but I am still getting nothing logged to the console when the object changes. The properties of the object and computed property are changing, as I have confirmed in Vue Tools. What am I doing wrong? Thanks
<v-btn small dark #click="test1.asdf = 'blah'">Test</v-btn>
data() {
return {
test1: {},
}
},
computed: {
test() {
return this.test1
}
},
watch: {
test: {
handler: function(val, oldVal) {
console.log(oldVal, val);
},
deep: true
}
}
Try this code, its works fine
<template>
<div id="app">
{{ test1 }}
<hr />
<button #click="test1.asdf = 'blah'">Click</button>
</div>
</template>
<script >
export default {
data() {
return {
test1: { asdf: "HEY" },
};
},
computed: {
test() {
return this.test1;
},
},
watch: {
test: {
handler: function (val, oldVal) {
console.log(oldVal, val);
},
deep: true,
},
},
};
</script>
I'd guess in your case you should add .native at end of your click event like this #click.native="test1.asdf = 'blah'"
Just tried by replacing the whole object. It works pretty well, and there is no need to initialize your object with the asdf property in your data:
<v-btn small dark #click="test1 = { ...test1, asdf: 'blah'}">Test</v-btn>
The spread syntax ...test1 helps keeping other properties in the target object if there is any, so you can safely add the asdf property without worrying about losing anything.
JSFiddle: https://jsfiddle.net/vh72a3bs/22/
#BadCoder Objects and Arrays are pass by reference in JavaScript, not pass by value. This means that when you add a key to the object as you are you doing in your question, you're just adding to the same Object. It's contents have changed but your variable test1 is still referencing the original object and unaware that its contents have updated. The watcher doesn't pick this change up. You can add deep: true to your watcher as another answerer has suggested, but this only watches for a couple of levels deep, so not ideal if you have a large object with lots of nested data. The most reliable way to trigger a watcher when dealing with arrays or objects is to create a new instance of that object. You can achieve this with object destructing.
Something like,
<v-btn small dark #click="test1 = { ...test1, asdf: 'blah'}">Test</v-btn>
works because you're creating a new object (using the previous objects attributes, plus anything new), triggering the watcher.
I have created a table component which accept dynamic data (th, tr, td,...).
Table data (td) could be a dynamic component as below:
<td>
<component
:is="data.content"
:colspan="data.colspan"
v-bind="data.props"
v-on="data.events"/>
</td>
...
export default {
name: "DynamicTable",
props: {
...
isLoading : { // loading slot will be used if true
type: Boolean,
default: false
}
}
}
I feed required data in another component like this:
<other-html-elements/>
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
:is-loading="isLoading">
...
computed: { ...
tableRows () {...
new TableData(CancelOrderButton, 'component', {
props: {
order
},
events: {
'updateLoadingStatus': this.updateLoadingStatus
}
})
...
methods: { ...
updateLoadingStatus (status) {
this.isLoading = status
}
and here is my CancelOrderButton:
methods: {
cancelOrder () {
this.$emit('updateLoadingStatus', true)
somePromise().finally(() => {
this.$emit('updateLoadingStatus', false)
})
once I click on a button and invoke the cancelOrder method, the updateLoadingStatus will be emitted without any problem. and after the promise settled, it will be emitted again. but the handler will not triggered.
I have checked everything. I'm sure that events are emitted. this problem will be fixed when I move the second emit statement out of the finally block or I if do not pass isLoading as a props for the dynamicTable.
Try setting the prop for that emit like this:
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
#update-loading-status="updateLoadingStatus"
:is-loading="isLoading">
And calling that emit like this (although it should work as you have it):
this.$emit('update-loading-status', true)
Also you can define them in a general way and use them in the component you want:
https://v2.vuejs.org/v2/guide/custom-directive.html
I am attempting to set a computed property based on the width of an element in my component. The problem is that I do not know how to get the width in a way that is reactive.
I've got a ref on my element to get the width which works. However I can't seem to get vue to detect that the width is changing
I have the element set up as:
<div ref="myDiv"></div>
and my computed property as:
myProperty() {
return this.$refs.myDiv.clientWidth/2;
}
myProperty evaluates correctly, but does not change as the width of myDiv changes
You can listen to resize event
data() {
return {
clientWidth: 0
}
},
// bind event handlers to the `handleResize` method (defined below)
mounted: function () {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy: function () {
window.removeEventListener('resize', this.handleResize)
},
methods: {
// whenever the document is resized, re-set the 'clientWidth' variable
handleResize (event) {
if (this.$refs.myDiv) {
this.clientWidth = this.$refs.myDiv.clientWidth
}
}
}
then you can use this.clientWidth where you want to get clientWidth.
myProperty() {
return this.clientWidth/2;
}
Another option for future readers of this question is to use a simple mixin I created https://github.com/Circuit8/vue-computed-dimensions. It creates computed properties for the dimensions and position of any ref you like, as in the example here:
<template>
<div ref="wrapper">
...
<div ref="other">...</div>
</div>
</template>
<script>
import computedDimensions from "vue-computed-dimensions";
export default {
// computedDimensions accepts a list of refs to use.
// each ref provided will produce 4 computed properties
// in this example we will have wrapperWidth wrapperHeight wrapperX and wrapperY. As well as otherWidth, otherHeight, otherX, and otherY.
// these can then be used in other computed properties to base reactivity on the rendered dimensions of an element
mixins: [computedDimensions("wrapper", "other")],
};
</script>
i'm try to change variable by watch and change it in to html
<p>{{customer.creditsLeft}}</p>
and vue
data() {
customer: {},
}
watch: {
'$store.state.jobs.listBooking.customer': function (newVal) {
this.customer.creditsLeft = newVal;
console.log('current credit now' + this.customer.creditsLeft);
return this.customer.creditsLeft;
}
},
console.log is woking but creditsLeft still not change. i'm a new bie in vue . pls help me
If you want to add new property to customer object you need to use set, otherwise it's not reactive.
this.$set(this.customer, 'creditsLeft', newVal)
https://v2.vuejs.org/v2/guide/reactivity.html
Or you can set it before hand so you don't need to use set
data() {
customer: {
creditsLeft: 0
},
}
How do I access $refs inside computed? It's always undefined the first time the computed property is run.
Going to answer my own question here, I couldn't find a satisfactory answer anywhere else. Sometimes you just need access to a dom element to make some calculations. Hopefully this is helpful to others.
I had to trick Vue to update the computed property once the component was mounted.
Vue.component('my-component', {
data(){
return {
isMounted: false
}
},
computed:{
property(){
if(!this.isMounted)
return;
// this.$refs is available
}
},
mounted(){
this.isMounted = true;
}
})
I think it is important to quote the Vue js guide:
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
It is therefore not something you're supposed to do, although you can always hack your way around it.
If you need the $refs after an v-if you could use the updated() hook.
<div v-if="myProp"></div>
updated() {
if (!this.myProp) return;
/// this.$refs is available
},
I just came with this same problem and realized that this is the type of situation that computed properties will not work.
According to the current documentation (https://v2.vuejs.org/v2/guide/computed.html):
"[...]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"
So, what (probably) happen in these situations is that finishing the mounted lifecycle of the component and setting the refs doesn't count as a reactive change on the dependencies of the computed property.
For example, in my case I have a button that need to be disabled when there is no selected row in my ref table.
So, this code will not work:
<button :disabled="!anySelected">Test</button>
computed: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
What you can do is replace the computed property to a method, and that should work properly:
<button :disabled="!anySelected()">Test</button>
methods: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
For others users like me that need just pass some data to prop, I used data instead of computed
Vue.component('my-component', {
data(){
return {
myProp: null
}
},
mounted(){
this.myProp= 'hello'
//$refs is available
// this.myProp is reactive, bind will work to property
}
})
Use property binding if you want. :disabled prop is reactive in this case
<button :disabled="$refs.email ? $refs.email.$v.$invalid : true">Login</button>
But to check two fields i found no other way as dummy method:
<button :disabled="$refs.password ? checkIsValid($refs.email.$v.$invalid, $refs.password.$v.$invalid) : true">
{{data.submitButton.value}}
</button>
methods: {
checkIsValid(email, password) {
return email || password;
}
}
I was in a similar situation and I fixed it with:
data: () => {
return {
foo: null,
}, // data
And then you watch the variable:
watch: {
foo: function() {
if(this.$refs)
this.myVideo = this.$refs.webcam.$el;
return null;
},
} // watch
Notice the if that evaluates the existence of this.$refs and when it changes you get your data.
What I did is to store the references into a data property. Then, I populate this data attribute in mounted event.
data() {
return {
childComps: [] // reference to child comps
}
},
methods: {
// method to populate the data array
getChildComponent() {
var listComps = [];
if (this.$refs && this.$refs.childComps) {
this.$refs.childComps.forEach(comp => {
listComps.push(comp);
});
}
return this.childComps = listComps;
}
},
mounted() {
// Populates only when it is mounted
this.getChildComponent();
},
computed: {
propBasedOnComps() {
var total = 0;
// reference not to $refs but to data childComps array
this.childComps.forEach(comp => {
total += comp.compPropOrMethod;
});
return total;
}
}
Another approach is to avoid $refs completely and just subscribe to events from the child component.
It requires an explicit setter in the child component, but it is reactive and not dependent on mount timing.
Parent component:
<script>
{
data() {
return {
childFoo: null,
}
}
}
</script>
<template>
<div>
<Child #foo="childFoo = $event" />
<!-- reacts to the child foo property -->
{{ childFoo }}
</div>
</template>
Child component:
{
data() {
const data = {
foo: null,
}
this.$emit('foo', data)
return data
},
emits: ['foo'],
methods: {
setFoo(foo) {
this.foo = foo
this.$emit('foo', foo)
}
}
}
<!-- template that calls setFoo e.g. on click -->