Im trying to pass data from my parent component to the child component's multiselect drop-downs. I have four but have only included one for example purposes.
If I duplicate the code from the parent to child to get 'uniquelocations', it works okay but I would prefer not to duplicate the code for obvious reasons. I need the code to be in the Parent for other functions.
I managed to get the function ' #on-reset="resetForm"' to work okay but I cant get this to pull in the data. Im new to this so probably missing something obvious.
Any help would be greatly appreciated.
Parent component
<Child
#on-reset="resetForm"
:passUniqueLocations="returnUniqueLocations" />
methods: {
returnUniqueLocations() {
return this.uniquelocations;
},
Child component
<Multiselectlocation
#click="passUniqueLocations()"
:hide-selected="true"
role="combobox"
aria-expanded="false"
aria-label="combobox"
title="Select a location"
id="MultiLocation"
v-model="locations"
**:options="uniquelocations"**
:close-on-select="true"
mode="tags"
:searchable="true"
placeholder="Select a location(s)"
:track-by="trackBy"
#search-change="results"
/>
props: {
passUniqueLocations: Function,
},
If you simply want to pass all the parent's attributes to a child, use Fallthrough Attributes while disabling attribute inheritance.
If you want to pick and choose a few specific members of the parent's this and spread them onto a child, without having to bloat the <Child /> tag by specifying each as a separate attribute, use a computed and v-bind:
<script>
export default {
props: ['p1', 'p2']
data: () => ({
d1: 'foo',
d2: 'bar'
}),
methods() {
m1() {
console.log('m1 was called')
},
m2() {
console.log('m2 was called')
}
},
computed: {
c1() {
return 'foo'
},
c2() {
return this.$store.state.c2
},
childAttributes() {
// extract as many things as you want from `this`
const { p1, p2, d1, d2, c1, c2, m1, m2 } = this
// return them as an object
return { p1, p2, d1, d2, c1, c2, m1, m2 }
}
}
}
</script>
<template>
<Child v-bind="childAttributes" />
</template>
If you have a really long list of members and you're bugged by the fact you need to specify each of them twice inside childAttributes, you might want to give pick a chance:
import { pick } from 'lodash-es'
export default {
//...
computed: {
childAttributes() {
return pick(this, ["p1", "p2", "d1", "d2", "c1", "c2", "m1", "m2"]);
},
},
};
Motto: all we are saaayiiing... / is give pick a chaaance...
Note: my answer focuses on Options API because that's what you seem to be using. In Composition API you have multiple ways of doing the above but probably the most straight forward would be using a reactive object (+ v-bind, as above):
const props = defineProps(/*...*/)
const childAttributes = reactive({
...props,
// other refs or computed
})
Related
how can I add components dynamically in component?
notice : i don't want to save component global. i just want to add components locally.
best way for define problem is show code
export default {
name: 'tabMaker',
props: {
components_: {
type: Array,
default: [],
},
},
components: {
// how can add components dynamically in here ?
},
data() {
return {}
},
created() {
var self=this;
this.components_.forEach((item)=>{
Object.entries(item).forEach(([key, value]) => {
// key = component name
// value = object component
// ????
// add component in props in object componenents
self.components[key]=value;// not work ? TODO
// ?????
});
})
},
}
You can pass the names of components, e. g. ['comp1', 'comp2'], then you have to register all the components, that could be passed (components: {comp1, comp2, comp3...}), and then you can use this structure:
<component v-for="(component, key) in components" :key="key" :is="component" />
this method would render the components you passed
I know that the vue model is a unidirectional data flow of props.
However, when prop is a reference object, the component can directly modify its properties. This is wrong, but vue will not check it.
I hope there is a mechanism to ensure that the component cannot modify the props (even if it is a reference object), rather than being checked by the developer.
For example, I have a component
<template>
<input v-model="obj.text" />
</template>
<script>
export default {
props: ['obj']
};
</script>
And a page that uses it
<template>
<my-template :obj="myobj"></my-template>
</template>
<script>
export default {
data() {
myobj: {
text: "hello";
}
}
};
</script>
When data changes in 'input', myobj.text will change together. This violates the unidirectional data flow.
Of course, as shown in the answer, I can use the "get" and "set" methods of the "computed".
But I must be careful not to write 'obj.someProperty' to any 'v-model', but this requires my own attention.
I hope there is a mechanism to give a hint when I make a mistake.
Couldn't find an existing duplicate so here's an answer. If anyone can find one, let me know and I'll make this one a Community wiki.
Use a computed property with getter and setter to represent your v-model value.
The getter gets the value from the prop and the setter emits the new value to the parent.
For example
<input v-model="computedProp">
props: ['referenceObject'],
computed: {
computedProp: {
get () {
return this.referenceObject.someProperty
},
set (val) {
this.$emit('updated', val)
}
}
}
and in the parent
<SomeComponent :reference-object="refObject" #updated="updateRefObject">
data: () => ({ refObject: { someProperty: 'initial value' } }),
methods: {
updateRefObject (newVal) {
this.refObject.someProperty = newVal
}
}
Let's say I have a prop such as
[{id:1, name:"first"}, {id:2, name:"second"}]
I have a watcher for this called Points. as soon as parent component changes this array, watcher function gets called in child.
Now, I also want to watch if name field in any of this array's object got changed. Workaround I know is to set deep as true in watcher, but this means this is gonna make the watcher for each property of the object. As I have a very huge array of huge objects, I don't want to make this many watcher.
So is there a way to make watcher for the whole array and also one of the properties of the array's object?
You can mark the non-reactive properties as non-configurable, and Vue will not detect changes in their values.
let arr = [{id:1, name:"first"}, {id:2, name:"second"}];
arr.forEach(item => Object.defineProperty(item, "id", {configurable: false}));
Alternatively, you could use a shallow watcher and require code that modifies the reactive properties to use Vue.set() instead of simple assignment.
Vue.set(arr[0], "name", "new name"); // don't use arr[0].name = "new name";
You can create a child component, where you bind objects to the array and place the watcher inside the child component as below:
Parent.vue
<template>
<child-component v-for="point in points" :point="point" ></child-component>
</template>
data: {
return {
points: [{id:1, name:"first"}, {id:2, name:"second"}]
}
}
Child.vue:
props: ['point']
...
watch: {
name: function(newVal){
// watch name field
}
}
One way would be to create a computed property that just touches the bits you care about and then watch that instead.
new Vue({
el: '#app',
data () {
return {
points: [{id: 1, name: 'first'}, {id: 2, name: 'second'}]
}
},
computed: {
pointStuffICareAbout () {
return this.points.map(point => point.name)
}
},
methods: {
updateId () {
this.points[0].id = Math.round(Math.random() * 1000)
},
updateName () {
this.points[0].name = Math.random().toString(36).slice(2, 7)
},
addItem () {
this.points.push({id: 3, name: 'third'})
}
},
watch: {
pointStuffICareAbout () {
console.log('watcher triggered')
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="updateId">Update id</button>
<button #click="updateName">Update name</button>
<button #click="addItem">Add item</button>
<p>
{{ points }}
</p>
</div>
I'm working on an app that allows you to capture and edit soccer match results.
there is a Matches component that makes an AP call to get the data of multiple matches from a server (match_list) and then renders a bunch of Match components, passing the data as props to these sub-components to fill their initial values.
<component :is="Match" v-for="match in match_list"
v-bind:key="match.id"
v-bind="match"></component>
On the Match component, I accept all the values as props.
But I get a warning that props shouldn't be edited and these should be data elements instead. So I tried passing them to the component data.
export default {
name: "Match",
props: ['local_team', 'visitor_team', 'localScore', 'visitorScore', 'date', 'time', 'location', 'matchId'],
data(){
return{
id: this.id,
local_team: this.local_team,
visitor_team: this.visitor_team,
location: this.location,
date: this.date,
time: this.time,
localScore: this.localScore,
visitorScore: this.visitorScore
}
},
Now I get a warning that editable data shouldn't be based on props.
How can I make the data from the Match component editable so it safely propagates to the parent component?
You need to accept your match object on the component's props, and make a copy of it on data (to be used as a model for your inputs). When your model changes you should emit that change to the parent so that it can change its own data appropriately (which then gets passed and reflected correctly through the child's props):
In this example I watch for any changes to the model and then emit the event directly, you can of course replace that behavior by having a submit button that fires the event upon click or something.
Vue.component('match', {
template: `
<div>
<p>{{match.name}}</p>
<input v-model="matchModel.name" />
</div>
`,
props: ['match'],
data() {
return {
matchModel: Object.assign({}, this.match)
}
},
watch: {
matchModel: {
handler(val) {
this.$emit('match-change', val)
},
deep: true,
}
}
});
new Vue({
el: "#app",
data: {
matches: [{
id: 1,
name: 'first match'
},
{
id: 2,
name: 'second match'
}
]
},
methods: {
onMatchChange(id, newMatch) {
const match = this.matches.find((m) => m.id == id);
Object.assign(match, newMatch);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<match v-for="match in matches" :match="match" :key="match.id" #match-change="onMatchChange(match.id, $event)"></match>
</div>
I understand the .sync modifier returned in Vue 2.3, and am using it for a simple child component which implements a 'multiple-choice' question and answer. The parent component calls the child like this:
<question
:stem="What is your favourite colour?"
:options="['Blue', 'No, wait, aaaaargh!']
:answer.sync="userChoice"
>
The parent has a string data element userChoice to store the result from the child component. The child presents the question and radio buttons for the options. The essential bits of the child look like this (I'm using Quasar, hence q-radio):
<template>
<div>
<h5>{{stem}}</h5>
<div class="option" v-for="opt in options">
<label >
<q-radio v-model="option" :val="opt.val" #input="handleInput"></q-radio>
{{opt.text}}
</label>
</div>
</div>
</template>
export default {
props: {
stem: String,
options: Array,
answer: String
},
data: () => ({
option: null
}),
methods: {
handleInput () {
this.$emit('update:answer', this.option)
}
}
}
This is all working fine, apart from the fact that if the parent then changes the value of userChoice due to something else happening in the app, the child doesn't update the radio buttons. I had to include this watch in the child:
watch: {
answer () {
this.option = this.answer
}
}
But it feels a little redundant, and I was worried that emitting the event to update the parent's data would in fact cause the child 'watch' event to also fire. In this case it would have no effect other than wasting a few cycles, but if it was logging or counting anything, that would be a false positive...
Maybe that is the correct solution for true 2-way binding (i.e. dynamic Parent → Child, as well as Child → Parent). Did I miss something about how to connect the 'in' and 'out' data on both sides?
In case you're wondering, the most common case of the parent wanting to change 'userChoice' would be in response to a 'Clear Answers' button which would set userChoice back to an empty string. That should have the effect of 'unsetting' all the radio buttons.
Your construction had some oddities that didn't work, but basically answer.sync works if you propagate it down to the q-radio component where the changing happens. Changing the answer in the parent is handled properly, but to clear values, it seems you need to set it to an object rather than null (I think this is because it needs to be assignable).
Update
Your setup of options is a notable thing that didn't work.
I use answer in the q-radio to control its checked state (v-model has special behavior in a radio, which is why I use value in conjunction with v-model). From your comment, it looks like q-radio wants to have a value it can set. You ought to be able to do that with a computed based on answer, which you would use instead of your option data item: the get returns answer, and the set does the emit. I have updated my snippet to use the val prop for q-radio plus the computed I describe. The proxyAnswer emits an update event, which is what the .sync modifier wants. I also implemented q-radio using a proxy computed, but that's just to get the behavior that should already be baked-into your q-radio.
(What I describe is effectively what you're doing with a data item and a watcher, but a computed is a nicer way to encapsulate that).
new Vue({
el: '#app',
data: {
userChoice: null,
options: ['Blue', 'No, wait, aaaaargh!'].map(v => ({
value: v,
text: v
}))
},
components: {
question: {
props: {
stem: String,
options: Array,
answer: String
},
computed: {
proxyAnswer: {
get() {
return this.answer;
},
set(newValue) {
this.$emit('update:answer', newValue);
}
}
},
components: {
qRadio: {
props: ['value', 'val'],
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
}
}
}
}
},
methods: {
clearSelection() {
this.userChoice = {};
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
<question stem="What is your favourite colour?" :options="options" :answer.sync="userChoice" inline-template>
<div>
<h5>{{stem}}</h5>
<div class="option" v-for="opt in options">
<div>Answer={{answer && answer.text}}, option={{opt.text}}</div>
<label>
<q-radio :val="opt" v-model="proxyAnswer" inline-template>
<input type="radio" :value="val" v-model="proxyValue">
</q-radio>
{{opt.text}}
</label>
</div>
</div>
</question>
<button #click="clearSelection">Clear</button>
</div>