Reputation: 4752
For example, if I have
<question-dialog /> /* hidden */
<show-dialog-button />
<answer-dialog /> /* hidden */
<show-dialog-button />
I now want each of the 2 buttons to affect their preceding dialog - the first button changes a property of the question-dialog
, and the second a property of answer-dialog
. One way I think I can achieve this is to bind all 4 to the model for my current page, and in mount()
, wire them up appropriately. Is there a better way? For example, can I do something like:
<question-dialog v-id="questionDialog"/> /* hidden */
<show-dialog-button v-prop:dialog="questionDialog"/>
<answer-dialog /> /* hidden */
<show-dialog-button />
Or maybe there's a completely different way that I'm not aware of?
(If it matters, I'm using typescript for all my components)
Upvotes: 1
Views: 102
Reputation: 135782
You can pass refs to those components:
<question-dialog ref="questionDialog"/> /* hidden */
<show-dialog-button v-prop:dialog="$refs.questionDialog"/>
And call their methods directly. But there is a caveat: since ref
s aren't reactive and are only available after mount, you have to force an update of the template after mount. See demo below.
Vue.component('answer-dialog', {
template: "#answer-dialog",
data() {
return {isShown: false}
},
methods: {
show() {
this.isShown = true;
}
}
});
Vue.component('show-dialog-button', {
template: "#show-dialog-button",
props: ['dialog'],
methods: {
showDialog() {
this.dialog.show();
}
}
})
new Vue({
el: '#app',
mounted() {
// force a re-render after mount, so the $refs are updated in the template
this.$forceUpdate();
}
})
<script src="https://unpkg.com/vue"></script>
<template id="answer-dialog">
<div>answer-dialog - open? {{ isShown }}</div>
</template>
<template id="show-dialog-button">
<div><button @click="showDialog">Click to toggle dialog</button></div>
</template>
<div id="app">
<answer-dialog ref="dialogOne"></answer-dialog>
<show-dialog-button :dialog="$refs.dialogOne"></show-dialog-button>
<answer-dialog ref="dialogTwo"></answer-dialog>
<show-dialog-button :dialog="$refs.dialogTwo"></show-dialog-button>
</div>
But a, maybe more idiomatic solution is to use an event hub and emit events that the sibling component can listen to:
var eventHub = new Vue(); // use a Vue instance as event hub
Vue.component('answer-dialog', {
template: "#answer-dialog",
props: ['dialogId'],
data() {
return {
open: false
}
},
created() {
eventHub.$on('open-dialog', (e) => {
if (e === this.dialogId) {
this.open = true;
}
});
}
});
Vue.component('show-dialog-button', {
template: "#show-dialog-button",
props: ['dialogId'],
methods: {
showDialog() {
eventHub.$emit('open-dialog', this.dialogId);
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<template id="answer-dialog">
<div>answer-dialog {{ dialogId }} - open? {{ open }}</div>
</template>
<template id="show-dialog-button">
<div><button @click="showDialog">Click to show dialog {{ dialogId }}</button></div>
</template>
<div id="app">
<answer-dialog :dialog-id="1"></answer-dialog>
<show-dialog-button :dialog-id="1"></show-dialog-button>
<answer-dialog :dialog-id="2"></answer-dialog>
<show-dialog-button :dialog-id="2"></show-dialog-button>
</div>
Or, even better, if you can, just use v-model
:
Vue.component('answer-dialog', {
template: "#answer-dialog",
props: ['show']
});
Vue.component('show-dialog-button', {
template: "#show-dialog-button",
props: ['value']
})
new Vue({
el: '#app',
data: {
dialogOneShow: false,
dialogTwoShow: false
}
})
<script src="https://unpkg.com/vue"></script>
<template id="answer-dialog">
<div>answer-dialog - open? {{ show }}</div>
</template>
<template id="show-dialog-button">
<div><button @click="$emit('input', !value)">Click to toggle dialog</button></div>
</template>
<div id="app">
<answer-dialog :show="dialogOneShow"></answer-dialog>
<show-dialog-button v-model="dialogOneShow"></show-dialog-button>
<answer-dialog :show="dialogTwoShow"></answer-dialog>
<show-dialog-button v-model="dialogTwoShow"></show-dialog-button>
</div>
Upvotes: 1