Building a project using Vue.js (and Laravel), the following (greatly simplified) code results in the below error:
Vue component:
<template>
<input type="text" class="form-control" v-model="main_object[item_id][my_answer_key]">
</template>
<script>
export default {
props: [
],
data() {
return {
main_object: {},
item_id: '1234',
my_answer_key: '5678'
}
},
ready: function () {
vm = this;
},
methods: {
}
}
</script>
Error received:
We know you can use the vm.$set() method to add properties to the object. However, we’re building the model path on the fly (item_id and my_answer_key change depending on various user options being selected). It seems like we have to write a method that determines if the object property is already set, and if it's not set, to then set it. Is there a better way to accomplish the above?
You could seemingly get around this by using the created hook and $set:
created: function () {
this.$set(this.item_id + "." + this.my_answer_key, "")
}
However, if item_id and my_answer_key can change, this approach will ultimately not work because Vue does not have dynamic two way binding. In other words, the binding in your v-model will be created once and will not change later if either of the item_id or my_answer_key values change.
So, to accomplish something like this, you might need to resort to a kludge like using a v-if and toggling it to destroy and recreate the input (and it's binding). That might work.
Sometimes, computed's can help with these situations. Bind your input to a simple attribute on your data model and use a computed to generate the actual nested data model you need elsewhere.
Related
I have a small problem, I created a package to manage forms, everything worked correctly until I needed to create a dynamic form based on an API response.
Here is the package: https://github.com/nulvem/js-form
It can be installed with npm:
npm install #nulvem/js-form
Here is my Vue component calling the Form class:
<script>
import {Form} from '#nulvem/js-form'
export default {
data() {
return {
form: new Form({
template: {
value: null,
validation: {
rules: 'required',
messages: {
required: 'You must select the type of report'
}
}
}
})
}
},
mounted() {
this.form.template = 'random-id'
// These data will be obtained from an API
let fields = [
'start_date',
'end_date',
'wallets',
]
fields.forEach((field) => {
let fieldData = {
value: null,
validation: {
rules: 'required',
messages: {
required: `You must enter the field ${field.description}`
}
}
}
this.form.$addField(field, fieldData)
})
// Here we can only see the getter/setter of the template property that was passed when instantiating the Form class
console.log(this.form)
}
}
</script>
The problem is: The fields I passed to the form during the creation of new Form ({...}) instance are reactive, since when I add new fields by calling the this.form.$AddField() function the fields are not getting reactive.
The funny thing is that the construct of the Form class calls a function called this.form.$AddFields() that calls the this.form.$AddField() function several times.
Why it works inside the constructor and don't when called separated? How to fix this issue?
Any idea what might be going on? This is driving me crazy, I've been trying to find the problem for several hours and I can't find it.
Edit
As a quick fix we make a change in the package where it is possible to pass the Form instance as a third parameter to the $addFields function, so the attributes are reactive, however we would like this to work without having to pass this parameter.
VueJS lose the reactivity of properties that are added to your data after the render. You can read more about this, here: https://br.vuejs.org/v2/guide/reactivity.html.
A quick way to try to solve this problem is adding a this.$forceUpdate() everytime that you add a new property, but it also can bring another problems depending of your context.
Or you can try using Vue.set() method, instead of your $addField method. You can read more in the same link above.
remove the context and change this line for reactivity:
this.$initialValues[field] = fieldDeclaration.value;
=>>>
this.$initialValues[field] = fieldDeclaration.value;
this.$initialValues = Object.assign({}, this.$initialValues);
so I have a complex nested structure that is like so in my Vue app's data:
{
x: {
y: [{
z: {
a: 1
}
}]
}
}
Although I used v-model in a v-for with the child property, setting .z.a = 2 doesn't seem to trigger this in the UI. I figured ok, must be that I'm mutating a property without alerting Vue, no biggie I just need to use Vue.set.
So I tried the following:
Vue.set(app.x.y[0].z, "a", 2)
Vue.set(app.x.y[0], "z", {a:2})
Vue.set(app.x.y, 0, app.x.y[0]) // app.x.y[0] is definitely {z:{a: 2}}
Vue.set(app.x, "y", app.y)
app.x.y = app.x.y.map(_ => _)
While these all usually do the trick for me, in this case it doesn't seem to work. If it changes things, I'm using v-model instead of a traditional prop, so it would be synced back to the app. I wonder if perhaps this disassociates the app.x and the actual data property for x.
I'm looking for a way to trigger a DOM update or properly set the value in Vue. I've also tried an app.$forceUpdate() to no avail :/
EDIT:
While I wasn't able to make Vue observe the change by itself, I found that I had a function that was populating z after it had been initialized to {}. I assume Vue set watchers on z the moment it was initialized, and so did not observe any further changed (i.e. adding a in the next line). Changing this to populate every possible property on initialization, combined with any of the .set's above and a $forceUpdate, I was able to trigger a DOM update. It's a temporary workaround though and I'd really like to be able to have Vue automatically observe this update.
You can try the vue computed property or watcher. This will automatically detect the changed in your data and update the v-model data you have.
In this example a change in the returned value of message will automatically be applied to the input.
<input v-model="message">
computed: {
message(){
return {x:{y:[1,2,3]}}
}
}
For example we have the following input component:
<input type="text" v-model="example.modules.report.description.title"></input>
Full source
I don't want to define in data following object:
example: {
modules: {
report: {
description: {
title: ""
}
}
}
}
I anticipate that Vue.js will create that structure itself (just like Angular.js), but it doesn't do it.
Is there any way to solve this problem?
the data() method is a factory that initializes the model on which the component is bound.
The docs explains clearly how is supposed to make it work
You should declare the initial value on the JavaScript side, inside the data option of your component.
vuejs docs
If your model is so complex to require all these nested compositions maybe you have to be fine with it or design a different data model.......or create your model with your own factory
export default {
data : example()
}
I have this Vue.js code:
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
return this['my' + 'Value']
}
}
})
As you can see the computed property will be cached and it is depended only on data.myValue. My question is how Vue.js caching system knows that run the computed function again only if myValue is changed?
If I change the myOtherValue variable, the myComputed function will use the cache, and will not be run again will I call it.
I thought about several ways how it is possible. But how Vuejs doing that?
I have read this article: https://v2.vuejs.org/v2/guide/computed.html and found no answer.
And what happen in this code, what it will be depeneded on?
const flag=2
new Vue({
data:{
myValue:'x',
myOtherValue:'y'
},
computed: {
myComputed: myFunction(){
if (flag==1){
return this['my' + 'Value']
}
else
return this['my' + 'Other' + 'Value']
}
}
})
Bonus: I will appreciate I link to the relevant function in the VueJS code: https://github.com/vuejs/vue
I will address only the specific question how does vue.js know which dependencies affect which computed property?
The simple answer is that each time vue evaluates a computed property it creates a map of all the reactive properties that were accessed in the span of that call. The next time any of these reactive properties change they will trigger a reevaluation of the computed property.
If during the most recent evaluation of a computed property, one of its reactive dependencies is never reached (maybe because it is within the non-traveled path of an if/else construct), subsequent changes to that reactive property will not trigger a reevaluation of the computed property.
Observe this behavior by modifying the two reactive properties in this fiddle (by simply typing in their corresponding input boxes). A few things to note:
the called computed property is evaluated once on document load (it's triggered because it's rendered in the template).
because the path is set to 1 the reactive property that will be mapped as a dependency is val1. As a result it will be the only one that can trigger a reevaluation of called when it changes. The value of val2 can also change but will not have the same effect on called, even though it's clearly present in the function.
When you click on the "Change Path" button, path is toggled from 1 to 2.
right after the path switch, note that a change to val1 will affect called only once more. Because path has been set to 2 prior to that last reevaluation, val1 will not be reachable and will not be mapped as a dependency of called any longer. Subsequent changes to its value won't trigger a reevaluation of called from that point on. But then val2 has now been mapped as a dependency of called and changes to it trigger the reevaluation the same way they did for val1 earlier. It will be so until the next path toggle from 2 back to 1.
Here's the code.
let path=1
let count=0
const vm=new Vue({
el:"#app",
data:{
val1:null,
val2:null,
},
computed: {
called: function(){
if (path==1){
this.val1
}
if (path==2){
this.val2
}
return "I was just called "+ ++count +" times"
}
},
methods: {
changePath(){
path = path==2 ? 1 : 2
}
}
})
and corresponding template
<div id="app">
<input v-model="val1"/> {{val1}}
<br>
<input v-model="val2"/> {{val2}}
<br>
<button #click="changePath">change path</button>
<br>
{{ called }}
</div>
It's the reactivity system of Vue.js, not a caching system.
The data in a component will be convert to getters and setters. When you access a value via a getter, the getter will add it to the dependencies, and when you modify the value via a setter, the setter will notify everyone who depends on the value.
Here is the source code, all the magic happens in this function: https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L131
From the docs it reads that:
Computed properties are cached, and only re-computed on reactive dependency changes.
However the following fiddle shows something a bit different.
https://jsfiddle.net/z11fe07p/267/
From the fiddle if you set the flag to 2, the computed property will be re-evaluated and executed if you change myOtherValue, however this will not happen if the flag is set to 1. I think it keeps track of your if conditions.
In the docs usually you can find links to the relevant source code.
Here is the code for computed properties:
https://github.com/search?utf8=%E2%9C%93&q=repo%3Avuejs%2Fvue+extension%3Ajs+%22computed%22&type=Code
I have a project that I've been working on for awhile that I might want to use vuejs for some of the UI elements. I fired up a tutorial and tried to piece together a basic example.
I have a basic javascript object like so:
var hex = {};
hex.turn_phase = 'unit_placement';
On my template, I have:
var app = new Vue({
el: '#vue-test',
data: {
message: 'Hello Vue!',
turn_phase: hex.turn_phase,
},
delimiters: ["<%","%>"],
mounted: function() {
this.fetch_turn_phase();
},
methods: {
fetch_turn_phase: function() {
Vue.set(this, 'turn_phase', hex.turn_phase);
},
}
});
This renders the correct turn_phase on the template initially, but if I change the hex.turn_phase in the browser console, the template doesn't react.
Is there something that I missed in this basic example?
It looks like you may have made things unnecessarily difficult. Just access your Vue instance via app?
Always make sure you go through the setters generated by Vue.js. These are watched and will re-render your component.
Instead, try using
app.turn_phase = 'unit_placement';
You can get a better understanding here, Reactivity
Vue creates all the data variables, computed properties and values returned from methods reactive. in your case since you
are changing hex, which is not a vue variable, so vue will not detect any changes in this variable. However if you change message variable, it will be reflective and will be changed in the template.