Vue JavaScript create variable from an other variable value - javascript

I can't find how to create a variable from another variable value.
I get this variable from AJAX call (using vue-resources):
langs: {1:'fr', 2:'en', 3:'ar'},
But, I don't know how many properties langs will have.
How can I get this variable dynamically:
newVar: {
has_lang: '1',
values: [{ is: 'fr', val:''}, { is: 'en', val:''}, { is: 'ar', val:''}]
},
So in newVar, the fr, en, and ar properties have been generated automatically using the values in langs.
I am planning to use the values array to bind to the values of my md-input inputs using v-model:
<md-input-container v-for="value in newVar.values">
<label>#{{ attribute.attribute }} #{{ value.is }}</label>
<md-input v-model="value.val"></md-input>
</md-input-container>
Is doing this manipulation the best practice? Because I can't find a solution which uses v-model for inputs that are generated from a v-for loop.

Use a watcher to set the newVar property whenever the langs property changes. You'll need to also do this during the mounted lifecycle event if you are passing in the langs variable as a prop or defining it within data().
In your case, I'd add a method to call at both points:
data() {
return {
langs: {1:'fr', 2:'en', 3:'ar'},
newVar: {},
};
},
methods: {
updateValues() {
let newVar = {
has_lang: (this.langs !== {}) ? '1' : '0',
values: [],
};
for (let lang in this.langs) {
newVar.values.push({ is: this.langs[lang], val: '' });
}
this.newVar = newVar;
}
},
watch: {
langs() {
this.updateValues();
}
},
mounted() {
this.updateValues();
}
Here's a CodePen.

Related

Data not getting updated in array of objects when the value in scope variable changes

I have an array of objects which is created on a button click. Now I have a scope variable that is assigned a default value.
This scope variable is part of textbox so now when user updates the textbox value that value is not getting automatically updated in all the records in bonus property of $scope.job.referralsrecords.
Code:
$scope.job.defaultBonus = 65;
function addNewReferrals{
$scope.job.referrals.push({
id: null,
bonus: $scope.job.defaultBonus
});
}
Can anyone tell me why is this happening and how do I update all bonus property in $scope.job.referrals when the $scope.job.defaultBonus gets change?
Listening for a change from an input field. You need ng-model and ng-change. ng-model binds the input value to the scope object.
<input ng-model='job.defaultBonus' ng-change='addNewReferrals()' />
You can iterate the array with map() and use the {...} spread operator to update the record.
function addNewReferrals() {
$scope.job.referrals = $scope.job.referrals.map(el => ({ ...el,
bonus: $scope.job.defaultBonus
}))
}
I set up an object called $scope for this snippet to demonstrate, but you would just use the angular property $scope.
const $scope = {
job: {
defaultBonus: 0,
referrals: [{
id: 1,
name: "John"
},
{
id: 2,
name: "Fred"
},
{
id: 3,
name: "Mary"
}
]
}
}
$scope.job.defaultBonus = 65;
function addNewReferrals() {
$scope.job.referrals = $scope.job.referrals.map(el => ({ ...el,
bonus: $scope.job.defaultBonus
}))
}
addNewReferrals();
console.log($scope);

watch: true for nested objects - NuxtJS (Vue), latest version

I have a form with many fields attached to a data - this.myData:
data: function() {
return {
isDataChanged: false,
myData: {},
myChangedData: {
default: '',
default1: {},
default2: []
},
}
},
myData is populated from a response from the server and it populates the form values.
myChangedData is for the new values, which are changed v-on:input="onChangeMyData($event, 'default')":
onChangeMyData(e, name, required = false){
const val = e.target.value.trim();
this.myChangedData[name] = val;
console.log(this.myChangedData)
this.checkIsmyDataChanged();
},
I can use the same method, providing a key as a second param. With the method checkIsmyDataChanged I am checking is it changed some field in the form. This method loops through myChangedData and compares its properties with changedData and if there is a difference this.isDataChanged = true.
The problem is that, I have a complicated structure of mydata/mydatachanged. default1 has objects in it and default1 is an array of objects. This means that, I can't use onChangeMyData, but other methods with different checks (validations) and now I need to call in all of them this.checkIsmyDataChanged();.
I created a watch for myChangedData:
watch:{
myChangedData: {
handler: function (newVal) {
console.log('change')
},
deep: true
},
},
, but it doesn't execute on change data
Did you try with Vue.set ? Source
Change this.myChangedData[name] = val; to
this.$set(this.myChangedData, 'name', val)
Thanks to that, the modification on the object should be detected and execute the watcher.

Why isn't the prop reactive in the dynamic component?

I have a dynamic component being injected in the slot of another one and I pass the props object to it. But when I update the data (an array) which has been assosiated with the prop (dataT: this.tableData), that prop isn't being updated inside the component.
It seems like I have a deal with two different objects but the array was passed by the reference, wasn't it?
This is the main component
<template>
<Button #click="addWindows"></Button>
<Window v-for="window in windows" :key="window.id">
<component :is="window.name" v-bind="window.props" #onDeleteRow="handleDeleteRow"></component>
</Window>
</template>
<script>
export default{
data(){
return{
windows:[],
tableData:[
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
methods:{
addWindows(){
this.windows = [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
},
{
id: 1,
name: 'Component2',
props: {}
}];
},
handleDeleteRow(id){
this.tableData = this.tableData.filter(r => r.id != id);
}
}
}
</script>
I expect updating dataT prop in Component1 when I modify this.tableData in the main component.
Original answer based on an earlier version of the question
If you make windows a computed property it can depend on tableData:
export default {
data() {
return {
tableData: [
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
computed: {
windows () {
return [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
}, {
id: 1,
name: 'Component2',
props: {}
}
]
}
}
}
If you can't make all of it a computed property, e.g. because you need to be able to modify it, then keep it as data and just use the computed property to create the array needed in your template. In that case the computed property would just be merging together different parts of the data into the correct form.
In your original code, the line dataT: this.tableData won't work because this.tableData doesn't exist yet, it'll just be undefined. There's no lazy evaluation here, it needs to resolve to the correct object at the point it hits that line.
Even if it was able to get access to the correct object it wouldn't help because in handleDeleteRow you're reassigning tableData to point to a different object. Passing 'by reference' has nothing to do with the name you use to identify the object, it refers to a reference in memory.
Incidentally, v-on also supports an object syntax, just like v-bind, so you could make onDeleteRow optional in a similar fashion.
Update based on the edited question
When you write this in addWindows:
props: {
dataT: this.tableData
}
This will assign the current value of this.tableData to dataT. That current value will be an array and as arrays are reference types any modifications made to that array will apply no matter what identifier is used to reference it.
However, this line...
this.tableData = this.tableData.filter(r => r.id != id);
... does not modify that array. Instead it assigns a totally new array to this.tableData. This will have no effect on the array referenced by dataT, which is unchanged.
There are several ways you could approach solving this, including using a computed property. However, a property getter might provide a convenient sleight-of-hand:
addWindows () {
const vm = this;
this.windows = [
{
id: 0,
name: 'Component1',
props: {
get dataT () {
return vm.tableData
}
}
},
{
id: 1,
name: 'Component2',
props: {}
}
];
}
This will always evaluate to the current value of tableData. Vue's reactivity should be fine with this extra indirection, it just sees it as equivalent to accessing tableData directly.
TL;DR
The issue is with your binding. Use the following:
<component
:is="window.name"
:dataT="window.props.dataT"
#onDeleteRow="handleDeleteRow">
</component>
Explanation
the v-bind attribute specifies what prop is bound to what value (or reference). In your case, you didn't specify what values you're binding to what props, thus the component props weren't bound as expected.

Prevent prop from overwriting the data

I'm new to vue.js and struggling with the following scenario.
I send an array filled with objects via props to my router-view.
Inside one of my router-view components I use this array in multiple functions, reference it with 'this.data' and safe it inside the functions in a new variable so I don't overwrite the actual prop data.
However the functions overwrite the original prop data and manipulate the data of the prop.
Here is an abstract example of my question:
App.vue
<template>
<div>
<router-view :data='data'></router-view>
</div>
</template>
<script>
export default {
data: function() {
return {
data: [],
};
},
created: function() {
this.getData();
},
methods: {
getData: function() {
this.data = // array of objects
},
}
route component:
<script>
export default {
props: {
data: Array,
},
data: function() {
return {
newData1 = [],
newData2 = [],
}
}
created: function() {
this.useData1();
this.useData2();
},
methods: {
useData1: function() {
let localData = this.data;
// do something with 'localData'
this.newData1 = localData;
}
useData2: function() {
let localData = this.data;
// do something with 'localData'
this.newData2 = localData;
}
}
}
</script>
The 'localData' in useData2 is manipulated from changes in useData1, whereby I don't overwrite the data prop.
Why do I overwrite the prop and how can i prevent it?
The problem you're experiencing a side effect of copying this.data by reference, rather than value.
The solution is to use a technique commonly referred to as cloning. Arrays can typically be cloned using spread syntax or Array.from().
See below for a practical example.
// Methods.
methods: {
// Use Data 1.
useData1: function() {
this.newData1 = [...this.data]
},
// Use Data 2.
useData2: function() {
this.newData2 = Array.from(this.data)
}
}
#Arman Charan is right on his answer. Object and arrays are not primitive types but reference.
There is an awesome video explanation here => JavaScript - Reference vs Primitive Values/ Types
So for reference types you first have to clone it on another variable and later modify this variable without the changes affecting the original data.
However for nested arrays and objects in high level the spead and Array.from will not work.
If you are using Lodash you can use _.cloneDeep() to clone an array or an object safely.
I like functional programming and I use Lodash which I strongly recommend.
So you can do:
let original_reference_type = [{ id:1 }, { id: 2 }]
let clone_original = _.cloneDeep(original_reference_type)
clone_original[0].id = "updated"
console.log(original_reference_type) //[{ id:1 }, { id: 2 }] => will not change
console.log(clone_original) // [{ id: "updated" }, { id: 2 }]
Suggestion: For simple arrays and objects use:
Objects:
let clone_original_data = {...original_data} or
let clone_original_data = Object.assign({}, original_data)
Arrays:
let clone_original_data = [...original_data] or
let clonse_original_data = original_data.slice()
For complex and high nested arrays or Objects go with Lodash's _.cloneDeep()
I think this is most readable, "declarative" way:
First, install lodash npm i lodash. Then import desired function, not the whole library, and initialize your data with array from props.
<script>
import cloneDeep from 'lodash/cloneDeep'
export default {
props: {
data: Array
},
data () {
return {
// initialize once / non reactive
newData1: cloneDeep(this.data),
newData2: cloneDeep(this.data)
}
}
}
</script>

Vuejs : v-model with multiple checkbox in v-for

I have some questions about using v-model with "complex" object. Here's my code:
<v-list v-for="(facet, facetName) in getFacets" :key="facetName">
<v-list-tile-content v-for='valueFacet in facet" :key="valueFacet.key">
<v-checkbox v-model="selectedFacets[facetName]" :value="valueFacet.key"></v-checkbox>
</v-list-tile-content>
</v-list>
And here's my data / init:
data() {
return {
selectedFacets: {}
}
},
created: function() {
// I initialize my object with all my facets here
config.facets.forEarch(facet => {
this.selectedFacets[facet] = [];
});
}
Some explanation: each facet has multiple checkbox representing values. We can select multiple values. My object has to be like this :
selectedFacets: { facet1 : [value1, value2], facet2: [value12, value 13]
With this code, each time I select one value, it will remove the previous one.
I tried to initalize my object in my data like this without using created function:
data() {
return {
selectedFacets: { "facetName" : [], "facetName2" : []}
}
}
and it works fine. All my values for each facet are added in the right array. But I need to initialize my object and facet names with my conf and if I don't initialize my object in my data, it does not work. I tried with computed / created, by getting my object from my store, but it keeps adding value by removing the previous one.
Any idea about what I do wrong ? Thanks.
Just initialize your object with Vue.set:
created: function() {
config.facets.forEach(facet => {
Vue.set(this.selectedFacets, facet, []);
});
}
When component initializes template it doesn't know about selectedFacets[facetName] so to make it reactive and correct working with v-model you should use Vue.set mutator.

Categories

Resources