Vue reactivity issue, need some explanations - javascript

I've removed most useless parts of my code, so don't worry if this code doesn't really make sense, it's just to show you what's not working.
First, I create an array from a base array called objects:
objects: [
{
text: "I dont trigger stuff",
},
{
"text": "I dont trigger stuff",
},
{
text:"I trigger stuff",
activated: undefined,
},
],
And the create function
created() {
const newArray = [];
this.objects.forEach(anObj => {
anObj.activated = false;
newArray.push(anObj);
});
this.filteredObjects = newArray;
},
I initialize a property activated to false. In my real code I'm not using a forEach, but a find but the result is the same.
Then, I display a few buttons to trigger an "activation"
<button
v-for="(myObj, index) in filteredObjects"
:key="index"
#click="activateObject(myObj, index)">
{{ myObj.text }}
</button>
And the function being triggered is this one:
activateObject(anObj, anObjIndex) {
this.$set(this.filteredObjects[anObjIndex], 'activated', !anObj.activated)
},
My goal here is just to update the activated property.
To check if reactivity is working, I've got a watcher:
watch: {
filteredObjects: {
handler() {
alert('called')
},
deep: true,
}
},
I've got two questions:
1/ Since all activated properties are set to false for all objects, why is there only one working, the one with the property initially set to undefined?
2/ If I update my activation function to:
activateObject(anObj, anObjIndex) {
anObj.activated = !anObj.activated;
this.$set(this.filteredObjects, anObjIndex, anObj);
},
It works well. Can someone explain me why, and what is the difference?
In both cases, VueJS Devtools shows the updated values when clicking on refresh. It's a reactivity issue.
You can find a fiddle here:
https://jsfiddle.net/dv1jgneb/

From the Docs:
Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
This explains why only the third button "trigger stuff".
So, you may either add that attribute in the data(), or as said in the docs, use this.$set:
this.objects.forEach(anObj => {
this.$set(anObj, 'activated', false);
newArray.push(anObj);
});
JS Fiddle
Hope this helps!

Related

How to replace one object for another in Vuejs? Interface not changing

i am doing an app in Vuejs and i am stuck with a problem in replacing one object for another that comes from an API every 10 seconds. I have this at the moment:
watch: {
myObj() {
for (let [key, tuner] of Object.entries(myObj)) {
---- some code ----
}
}
},
},
created() {
setInterval(this.callMyApi(), 10000);
},
I am watching the object and then use it in a for loop that does some logic. I saw the documentation mention using 'this.$set()' but this only adds a property to the object and i want replace one for another. Thanks for the help.
I would use:
this.myObj = JSON.parse(JSON.stringify(newObj))
to make a deep copy of the object inside this.callMyApi().
To watch for changes on this object do the following: create a new bool variable and use it on the component you want to update:
<componenttoupdate :reactto=objupdated/>
data: {
...
objupdated: false
...
}
and switch the objupdated bool state anytime you do the deep copy on your function, and so the component will update.
This is a cheaper solution than keeping watchers over large objects that may get updated very often as yours.

Vuejs does not update my interface, anyone know why?

I have an app with vuejs. I have an object that is being updated from an API every 10 seconds, it must also change the Html (interface) when it's updated.
I am getting some difficulties in updating the interface although I can see the object changing in the console, therefore I know its changing.
Here is a piece of my code:
watch: {
myObj() {
for (let [key, tuner] of Object.entries(myObj)) {
---- some code ----
}
}
},
},
created() {
setInterval(this.callMyApi(), 10000);
},
I call my API every 10 seconds and then i use the WATCH property to check when myObj changes and the do some logic with it. Can anyone help me?
There are 2 components to this that are extremely important to ensure that watching an object -- especially with nested properties -- works correctly.
First, you must either A) instantiate the property as an object by declaring myObj: {} or B) use Vue.set(this, 'myObj', yourCustomObject) to ensure that it is reactive.
Second, you must use deep: true, if your object contains nested objects/arrays.
watch: {
myObj: {
deep: true, // this is the important part
handler() {
// your code
}
}
}

v-model value update issue when passed prop value from parent to child

I am passing an object 'item' from parent to 'child' component. It works just fine. Please refer this.
As you change the values from dropdown, it updates the UI. My issue is that the same exact code does not work in my application (running locally on my machine). I even tried adding {{item.type}} in html, but it does not change (sticks to original value). One thing I noticed that, if I put #change='onChange' and printed the value in onChange method and it prints updated value.
Really unable to find solution to fix this. Any help would be great. Thanks.
The issue is you are adding the type property to your model after the item was already bound to data, and Vue cannot detect changes to properties added that way.
The fix is to make sure there is a type property on item,
item: {
"direct_sale_price": "",
"is_auction": true,
"is_tender": false,
"type": null
}
or to properly add it using $set.
created: function () {
if (this.item.is_auction) {
this.$set(this.item, 'type', 'auction')
} else if (this.item.direct_sale_price) {
this.$set(this.item, 'type', 'direct-sale')
} else if (this.item.is_tender) {
this.$set(this.item, 'type', 'tender')
} else {
this.$set(this.item, 'type', 'plain')
}
}

Vue object properties not changing with v-bind:class

I have a object which kind of looks like this:
{
data: [
{
id: 28,
type: "blabla",
name: "myname",
language: "en",
active: true
},
{
id: 5,
type: "blabla",
name: "myname2",
language: "fr",
active: false
},
// etc
]
}
I have split this object up into groups which I am display like this:
<li class="suggestion" v-for="suggestion in group">
</li>
I want to highlight my results so that is why I have a position property on my Vue object. which can be changed.
I have a watcher set up for the position attribute:
position() {
this.suggestions.forEach((suggestion, index) => {
suggestion.active = index === this.position;
});
}
It will make the current position(so the currently item active). This works fine when I am checking the array of objects and their properties. The items change their .active property perfectly. But the class binding does not seem to re-evaluate.
What is going wrong here? The initial active state does get taken into account and my first item is highlighted perfectly fine.
Nothing happens on a change to the actual classes though yet with the Vue devtools I can see perfectly fine that the objects properties are changing.
So the problem was that if object gets created like this:
object[suggestion.type].push(suggestion);
Vue doesn't detect changes to that object. It has to do with how Vue watches objects for changes.
In order for Vue to play nicely with objects you create and want to be reactive you need to utilize Vue.set
Vue.set(object, suggestion.type, suggestion)
Or
this.$set(object, suggestion.type, suggestion)

Working with current values of properties of the ViewModel with Knockout-Kendo.js

I recently found this great component -> Knockout-Kendo.js.
I use it to handle some behaviors with kendoComboBox.
The synchronization with the viewmodel works perfectly.
I want to listen changes of the control to execute some actions based on the current selected value.
I don't see any property that I can bind in the 'data-bind' attribute to listen changes but I know that internally, the knockout-kendo component listen changes and this is how the viewmodel is able to by sync with the control.
If I try to listen the valueChange event of the control, the problem is my eventhandler is catched before the viewmodel and while running in my eventhandler, I just have the previous value of the control using the viewmodel.
Look at this binding configuration in the component. What I understand is I'm able to use 'enabled', 'search', 'data', 'value' and any other exposed properties of the telerik control. What would be nice would be to define in data-bind attribute a property 'change' with an eventhandler linked in my viewmodel and be sure my eventhandler would be called after the internal eventhandler of the knockout-kendo component.
createBinding({
name: "kendoAutoComplete",
events: {
change: VALUE,
open: {
writeTo: ISOPEN,
value: true
},
close: {
writeTo: ISOPEN,
value: false
}
},
watch: {
enabled: ENABLE,
search: [SEARCH, CLOSE],
data: function(value) {
ko.kendo.setDataSource(this, value);
},
value: VALUE
}
});
I know I can try to modify the order of bind of events to be sure my eventhandler must be called after the synchronization of the viewmodel but I think it's a very bad practice.
Anybody have an idea how I can solve this problem with elegance?
You haven't mentioned why you want to do this. I can imagine two reasons:
To trigger some UI behavior/logic directly;
To trigger business logic (which may in turn trigger UI changes of course);
For people landing at this question with the latter case, here's an alternative solution. (This answer may not be a straight up answer to the OP's question, but seems useful enough to post it here.)
Suppose you have this basic view model:
var ViewModel = function() {
var self = this;
self.kendoObservable = ko.observable("Some text")
};
There are two ways you can indirectly respond to changes by Kendo. First, for simple cases, there's computed observables:
// Option 1, add this to ViewModel
self.dependentObservable = ko.computed(function() {
return self.kendoObservable() === "" ? "Empty" : "Not empty"; // example
});
This dependentObservable will be modified each time the kendoObservable changes. Basic stuff.
If you want to do something more complex when kendoObservable changes, e.g. do an AJAX call or whatnot, you may need a manual subscription:
// Option 2, add this to ViewModel
self.kendoObservable.subscribe(function(newValue) {
// Possibly do an AJAX call here or whatnot. Example:
alert("The new value is: " + newValue);
});
This will allow you to fire some complex logic each time the kendoObservable changes. AFAIK you need to check yourself whether the newValue is actually a changed value, at least in some versions of KO.

Categories