I'm trying to setup a multi select control from bootstrap-vue and bind it to a JSON object. The problem is that I need a computed value to get my json data format in a int array for the multiselect selected values and vice versa. Using such a computed property means that I change date while rendering which leads to an infinite loop.
Currently I created a computed property which has a getter which transforms the JSON object array in a integer array as well as a setter which does the opposite. In my example code the JSON object only contains the id, but in my production code there are a lot of other fields inside a "company".
<template>
<b-form>
<b-form-select
:id="`input-companies`"
v-model="companiesSelected"
multiple
:select-size="4"
:options="availableCompanies"
></b-form-select>
</b-form>
</template>
<script>
const availableCompanies = [
{ value: 1, text: 'company1' },
{ value: 2, text: 'company2' },
{ value: 3, text: 'company3' },
{ value: 4, text: 'company4' }
]
export default {
data () {
return {
employee: { id: 1, name: 'test', companies: [ { id: 1 }, { id: 2 } ] },
availableCompanies: availableCompanies
}
},
computed: {
companiesSelected: {
get () {
if (this.employee.companies == null) {
return []
}
return this.employee.companies.map(company => { return company.id } )
},
set (newValue) {
if (newValue == null) {
this.employee.companies = []
} else {
this.employee.companies = newValue.map(companyId => { return { id: companyId } })
}
}
}
}
}
</script>
The setting of this.employee.companies leads to a infinite loop. I don't really know how to avoid this. Does anyone know how to overcome this issue?
I basically split your computed set into the #change event and it seems to be working.
The #change event should only fire from user interactivity and should therefor cause the loop.
https://codepen.io/Hiws/pen/agVyNG?editors=1010
I'm not sure if that's enough for you, as i didn't take the extra fields on a company into consideration when writing the example above.
Related
I want to watch changes of families variable which contains nested objects
<component-test
v-for="family of familiesToDisplay"
// rest
/>
data: () => ({
families: [],
}),
computed: {
familiesToDisplay() {
return this.families.fillter(family => family.members > 4);
},
},
In some response I have seen some recomanding the use of watch but in my case I didn't knew how to implement it since I have never used before.
so the request is to get changes of nested objects in families (as objects I have person and work so if a name of a person has been changed or its work has been changed changes must be retreived here)
This code should work fine, however, it is necessary to correctly mutate objects inside an array. You need to use this.$set or replace the object with a new one to trigger vue re-rendering a list and recalculation of a list.
For example, you can change them like this, (full working example you can find here):
export default {
name: "HelloWorld",
components: {
Display,
},
data: () => ({
families: [
{ name: "foo-1", members: 1 },
{ name: "foo-5", members: 5 },
{ name: "bar-6", members: 6 },
{ name: "bar-3", members: 3 },
],
}),
methods: {
onClick() {
this.families.forEach((family) => {
this.$set(family, "members", this.getRandomNumber());
});
},
getRandomNumber() {
return Math.floor(Math.random() * 10) + 1;
},
},
};
I want to create a search filter. The way it works is a user inputs a text in the search bar, the input is stored using vuex and the result is shown in a different page. Here's an array of objects in a js file
export const productData = [
{
id: 1,
name: "table",
materials: "wood"
},
{
id: 2,
name: "table2",
materials: "metal"
},
{
id: 3,
name: "chair",
materials: "plastic"
}
]
I want to filter using the user's input. Here's my function
import { productData } from '#/data/productData'
export default {
data() {
return {
products: productData
}
},
computed: {
userInput() {
return this.$store.state.userInput
},
filterProducts: function() {
return this.products.filter(q => q.name.match(this.userInput))
}
}
}
When I console the userInput, it works fine! So the problem is in the filterProducts function. It shows an empty array if I console it. What am I doing wrong? Thank you.
edit: the reason I make a new variable called products is because the actual js file is more complex so I had to flatten the array. But the flatten process works fine so I thought I would just simplify the question.
The match function accepts a regex, not String. Give indexOf a try:
filterProducts: function() {
return this.products.filter(q => q.name.indexOf(this.userInput) >= 0)
}
Form array value changes is not working. I can't console the value inside subscriber method.
Here is my basic form array code below.
ordersData = [
{ id: 100, name: 'order 1' },
{ id: 200, name: 'order 2' },
{ id: 300, name: 'order 3' },
];
this.formGroup = this.formBuilder.group({
testControl: this.formBuilder.array([])
});
get formGroupControl() {
return (this.formGroup.get('testControl') as FormArray);
}
private addFormControl() {
this.ordersData.forEach((o, i) => {
(this.formGroup.get('testControl') as FormArray).controls.push(this.formBuilder.control(''));
});
}
I've called this addFormControl() function inside the ngOnInit() and if I try to see the valuechanges using this following way
this.formGroup.get('testControl').valueChanges.subscribe(value => {
console.log('log', {value});
});
this console is not working..
Walk me through the right way of handling form array
I was having the same problem (I think), forms added to the formArray through formArray.controls.push didn't work, I fixed by using formArray.push instead
In Vue.js, I have a data object with dynamically added/edited properties that are themselves arrays. For example, the property starts out as follows:
data: function () {
return {
vals: {}
};
}
And over time, through various button clicks, etc., vals may look like the following (with the actual property names and values being 100% dynamic based on a number of factors):
vals: {
set1: [
{
prop1: 123,
prop2: 'hello'
},
{
prop1: 456,
prop2: 'bye'
}
],
set2: [
{
prop3: 'Why?!',
prop4: false
}
]
}
As the array properties (i.e., set1 and set2) are changed, I want to be able to react to those changes.
For example, I may do something like the following in my code:
var prop = 'set1';
this.vals[prop].push({
{
prop1: 789,
prop2: 'hmmm...'
}
});
However, when I do that, the component is not updating (I presume because I am pushing an object onto the end of a subarray of an object; and Vue.js doesn't seem to track those changes).
I have been able to force the component to be reactive by doing this.$forceUpdate(); after the above push, but I imagine there has to be a more eloquent way of getting Vue.js to be reactive when it comes to objects being pushed onto the end of object subarrays.
Does anyone know of a better way to try to do what I am trying to achieve? Thank you.
Any time you're adding a new property to an object or changing a value within an array, you need to use Vue.set().
Vue.set(this.vals, prop, [ /* ... */ ])
Ideally, you should define all your properties up front so Vue doesn't have to invalidate computed properties depending on your data model's shape. Even if you have them set to null you should try to map out all the properties you expect your component to need.
Also, in your first code block, you have a colon after your return: which would evaluate to a label, meaning your data function isn't returning anything.
You could try something like this using lodash and a deep watcher..
More on deep watcher here
new Vue({
el: "#app",
methods: {
setValue() {
this.oldPeople = _.cloneDeep(this.people);
}
},
mounted() {
this.setValue();
},
el: "#app",
data: {
changed: null,
people: [
{ id: 0, name: "Bob", age: 27 },
{ id: 1, name: "Frank", age: 32 },
{ id: 2, name: "Joe", age: 38 }
],
oldPeople: []
},
watch: {
people: {
deep: true,
handler(after, before) {
// Return the object that changed
let vm = this;
let changed = after.filter(function(p, idx) {
return Object.keys(p).some(function(prop) {
return p[prop] !== vm.oldPeople[idx][prop];
});
});
vm.setValue();
this.changed = changed;
},
}
}
});
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
<div id="app">
<div>
<input type="text" v-for="(person, index) in people" v-model="people[index].age" />
<div v-if="changed !== null">
You changed:<br/>{{ changed }}
</div>
</div>
</div>
I want to change the property of an object similar to this, this is a simplified object with a few properties of the original:
state = {
pivotComuns: [
{
id: 1,
enabled : true
},
{
id: 2,
enabled : true
}
],
otherProperties : "otherProperties"
}
I'm changing the state of enabled like this:
state = {
...state,
pivotColumns: {
...state.pivotColumns,
[2]: {
...state.pivotColumns[2], enabled: !state.pivotColumns[2].enabled
}
}
}
It works, but instead of return an array like I is the pivotComuns property it returns an object, "notice that I change [] for {}":
state = {
pivotComuns: {
{
id: 1
enabled : true
},
{
id: 2,
enabled : true
}
},
otherProperties : "otherProperties"
}
What I'm doing wrong, I need to keep that property an array.
Very late post, but for future reference, you could do the following:
state = {
...state,
pivotColumns: state.pivotColumns.map(pc =>
pc.id === 2 ? {...pc, enabled:!pc.enabled} : pc
)
}
The advantage is that you will not change the object referenced in the "old array", you will instead insert a new object in its place. So if you would like to go back and forth in the state you can now do so.
example:
https://codepen.io/anon/pen/JyXqRe?editors=1111
I don't believe you can use the spread operator in such a way and in fact wouldn't recommend it if you could because it creates very hard to read code. There is a much simpler solution that I use on a daily basis when it comes to updating a key/value on an object where the value is an array:
var state = {
pivotColumns: [
{
id: 1,
enabled : true
}, {
id: 2,
enabled : true
}
],
otherProperties : "otherProperties"
}
var clonedPivotColumns = state.pivotColumns.slice();
clonedPivotColumns[1].enabled = !state.pivotColumns[1].enabled;
state = {
...state,
pivotColumns: clonedPivotColumns
}
this will get you the right results and will not cause any mutations.
working pen
http://codepen.io/finalfreq/pen/ggdJgQ?editors=1111