I have one problem. I have
data: {
tracks: []
}
And tracks array will contain a complex object. And when I assign a new value to tracks nested object to become reactivity. But I just don't need not deep reactivity object. How can I do it without created function or JSON.parse?
Because tracks used with Cesium framework and use Vue getter. And FPS becomes 10-15. Without Vue have 50-60 FPS
Use Object.freeze or Object.defineProperty (you only need configurable: false) to prevent Vue from picking up reactivity on large datasets
https://forum.vuejs.org/t/cesium-and-vue-js-data-getters/26928
You can implement a deep watcher on tracks:
watch: {
tracks: {
handler (newVal, oldVal) {
// implement what you want to do here
// If you just wanted to force a re-render you can do:
this.$forceUpdate()
}
deep: true,
}
Related
I'm defining my state with an object, initialized with some nested objects to an empty string and an empty array, as such:
state: {
displayedFarmer: {
name: "",
arrivalDates: []
// some more fields...
},
// more vuex stuff
}
I would expect that if I console.log the displayed farmer, arrivalDates would appear. Here's what I did to track it in my component:
computed: {
...mapState([ 'displayedFarmer' ]),
// more code
},
watch: {
displayedFarmer: {
handler() {
console.log("displayedFarmer", this.displayedFarmer);
},
deep: true,
immediate: true
}
}
The first log line appearing shows the displayedFarmer object, with the arrivalDates and name missing:
displayedFarmer
Object { … }
(basically only the prototype and the __ob__ objects appear when I expand it in the console)
That behavior is unclear to me, and has forced me to use a small and harmless hack to initialize the fields the first time they are being accessed.
For this question, what I want to know is:
Why can't I see the objects I initialized in my state when I access them via the component?
How can I do this differently, so that when I first access the object, all the nested items are initialized?
watch with immediate: true fires before components hook:created. This is probably the reason you don't have access to the initialized object.
I don't know what you are trying to achieve, can you expand your question?
The first thing that came to my mind is to try to call method (which does whatever you want) on a hook:mouted. Then call the same method in watcher, but with immediate: false (which is a default btw)
I have a watcher setup on an array and I have deep watch enabled on it, however the handler function does not trigger when the array changes, applications is defined in the object returned in data. Here's the code:
watch: {
applications: {
handler: function(val, oldVal) {
console.log('app changed');
},
deep: true,
},
page(newPage) {
console.log('Newpage', newPage);
},
},
Vue cannot detect some changes to an array such as when you directly set an item within the index:
e.g. arr[indexOfItem] = newValue
Here are some alternative ways to detect changes in an array:
Vue.set(arr, indexOfItem, newValue)
or
arr.splice(indexOfItem, 1, newValue)
You can find better understanding of Array Change Detection here
If you reset your array with arr[ index ] = 'some value', Vue doesn't track to this variable. It would better to use Vue array’s mutation method. These methods used to track array change detection by Vue.
It is worked for me.
Trying to use vue watch methods but it doesn't seem to trigger for some objects even with deep:true.
In my component, I recieve an array as a prop that are the fields to create
the following forms.
I can build the forms and dynamicly bind them to an object called crudModelCreate and everything works fine (i see in vue dev tools and even submiting the form works according to plan)
But I have a problem trying to watch the changes in that dynamic object.
<md-input v-for="(field, rowIndex) in fields" :key="field.id" v-model="crudModelCreate[field.name]" maxlength="250"></md-input>
...
data() {
return {
state: 1, // This gets changed somewhere in the middle and changes fine
crudModelCreate: {},
}
},
...
watch: {
'state': {
handler: function(val, oldVal) {
this.$emit("changedState", this.state);
// this works fine
},
},
'crudModelCreate': {
handler: function(val, oldVal) {
console.log("beep1")
this.$emit("updatedCreate", this.crudModelCreate);
// This doesn't work
},
deep: true,
immediate: true
},
}
From the docs
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. 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.
Please take a look to Reactivity in Depth https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
In certain circumstances it is possible to force a refresh by adding a key property to the child component containing a json string of the object being passed to it in v-model.
<Component v-model="deepObject" :key="JSON.stringify(deepObject)" />
Making an SPA using Polymer, and I need my custom components to all use a common custom component which represents my backend API and is responsible of GET-ting/POST-ing data from/to the API. It also serves as a "cache" and holds the data to display. This way, all the components that have access to this single element will share the same data.
So what I want to do is this... :
<my-api
users="{{users}}"
products="{{products}}">
</my-api>
...but programmatically, as <my-api> is not declared in all of my components but once in the top one and then passed down through the hierachy by JavaScript:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
if (oldVal)
oldVal.removeEventListener('users-changed', this._onDataChanged);
// Listen for data changes
newVal.addEventListener('users-changed', this._onDataChanged);
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // DOESN'T WORK as 'this' === <my-api>
this.products = this.api.products; // Plus I'd have to repeat for every array
}
});
Does Polymer offers a built-in way to do this ? Can I create a double curly braces binding programmatically ?
I would likely architect this slightly differently: passing down the products/users arrays declaratively taking advantage of Polymer's binding system. Or you could write your my-api element in such a way that they all share state and the first declared one is the primary while future declared ones are replicas. This would let you declare them wherever you need them and bind to the values via Polymer's normal ways.
But to answer your question, there's currently no way to easily programmatically setup the same kind of binding without using private Polymer APIs.
To avoid repeating as much and for the binding issue you were having you could use Polymer's built-in listen and unlisten methods:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
var apiProperties = ['users', 'products'];
if (oldVal) {
apiProperties.forEach(function(prop) {
this.unlisten(oldVal, prop + '-changed', '_onDataChanged');
});
}
// Listen for data changes
apiProperties.forEach(function(prop) {
this.listen(newVal, prop + '-changed', '_onDataChanged');
});
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // `this` should be the element now
this.products = this.api.products;
}
});
Given how this is a common pattern you're doing, you could probably get a lot of benefit out of extracting some of these things into a Behavior that abstracts away the binding/unbinding and API element forwarding.
Another optimization you may could make work would be to to look at the event passed to _onDataChanged to see if you can infer which value changed and update your corresponding property. This could prevent you needing to add a line for every property.
I ended up using an other solution. Instead of manually passing the top <my-api> element down the hierarchy any element that needs access to this shared data declares its own <my-api>.
Then in the <my-api> element's declaration I made that all instances use the same arrays references. So whenever I update one they all get updated, and I don't have to pass anything down the HTML hierarchy, which makes things a LOT simpler.
I want to call a function drawChart() when either my string chartTitle or when any of the properties in my object chartOptions changes.
Watching them individually is easy:
$scope.$watch('chartTitle', drawChart);
$scope.$watchCollection('chartOptions', drawChart);
However, I need a way to combine them into one $watch statement (without adding chartTitle as a property of chartOptions.
How can I prevent both of these watchers from firing in the same digest cycle. That is, if I change both chartTitle and a property on chartOptions, how can I prevent drawChart from being called twice?
Possible approaches:
Do a deep watch on chartOptions and chartTitle. Unfortunately chartOptions is an extremely large and complicated object, so doing a deep watch on these properties is infeasible. Is it possible to put a limit on how deep to go?
Detect in the chartOptions watcher whether chartTitle has been changed during this digest cycle, and vice versa. Is this even possible?
Figure out a watch expression that would capture all the properties on chartOptions and chartTitle.
You can make a deep watch:
$scope.$watch(
function() {
return {
chartTitle: $scope.chartTitle,
chartOptions: $scope.chartOptions
};
},
function(newval) {
...
},
true // deep watch
);
Keep an eye for performance problems, it will be creating an object each digest cycle. Normally it won't matter, but for a heavyweight application it might.
Combine chartTitle and chartOptions into a single object, then do a $watchCollection on that object.
$scope.$watchCollection(function() {
// Make a shallow clone of all the properties in chartOptions
var amalgam = {};
for (var key in $scope.chartOptions) {
var val = $scope.chartOptions[key];
amalgam[key] = val;
}
// Add the chart title to our shallow clone
amalgam.chartTitle = $scope.chartTitle;
return amalgam;
}, drawChart);
Note this may not be very fast as you are creating a new object every digest cycle.