I know this has been asked several times before, but as a Vue.js beginner I had trouble interpreting some of the other discussions and applying them to my situation. Using this CodeSandbox example, how would one pass the indicated object from "Hello" to "Goodbye" when the corresponding button is pressed? I'm unsure if I should be trying to use props, a global event bus, a plugin, vuex, or simply some sort of global variable.
Edit:
Here is the code for App.vue, Hello.vue and Goodbye.vue (from the previously linked CodeSandbox example).
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "app"
};
</script>
Hello.vue:
<template>
<div class="hello">
<h1>This is Hello</h1>
<div v-for="(obj, index) in objects" :key="index">
<router-link class="button" :to="{ path: '/goodbye'}">Share obj[{{ index }}] with Goodbye</router-link>
</div>
</div>
</template>
<script>
export default {
name: "hello",
data() {
return {
objects: [0, 1, 2, 3]
};
}
};
</script>
Goodbye.vue:
<template>
<div class="goodbye">
<h1>This is Goodbye</h1>
<p>Obj = "???"</p>
<router-link class="button" :to="{ path: '/hello'}">Hello</router-link>
</div>
</template>
<script>
export default {
name: "goodbye"
};
</script>
Props are used to share data with child components. Since the components never exist at the same time, this is not useful for you. Similarly, events are not very useful to you here. You can send an event on a global bus, but since the other component does not exist yet, it cannot listen for the event.
I am not sure what you would want to do with a plugin in this case. You should never use a global variable, unless you have a very good reason to (e.g. you use Google Analytics, which happens to use a global variable, or you want to expose something within Vue in development mode for debugging purposes). In your case, you likely want to change some global app state, which is exactly what Vuex was made for. Call a Vuex mutator or action either when clicking, or in a router hook such as router.beforeEach to save the information in a structured manner so you can then retrieve it with a mapped getter. Keep in mind that you want to structure your vuex store, so don't use a state variable thingsIWantToShareWithGoodbye, but instead split it up in previousPage, lastClickOffset and numberOfClicks.
For example:
// store/index.js
import Vuex from "vuex";
import Vue from "vue";
Vue.use(Vuex);
const state = {
button: null
};
const getters = {
button(state) {
return state.button;
}
};
const mutations = {
setButton(state, payload) {
state.button = payload;
}
};
export default new Vuex.Store({
state,
getters,
mutations
});
// Hello.vue
<template>
<div class="hello">
<h1>This is Hello</h1>
<div v-for="(obj, index) in objects" :key="index">
<router-link #click.native="setButtonState(obj)" class="button" :to="{ path: '/goodbye'}">Share obj[{{ index }}] with Goodbye</router-link>
</div>
</div>
</template>
<script>
export default {
name: "hello",
data() {
return {
objects: [0, 1, 2, 3]
};
},
methods: {
setButtonState (obj) {
this.$store.commit('setButton', obj)
}
}
};
</script>
// Goodbye.vue
<template>
<div class="goodbye">
<h1>This is Goodbye</h1>
<p>Obj = {{ button }}</p>
<router-link class="button" :to="{ path: '/hello'}">Hello</router-link>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "goodbye",
computed: {
...mapGetters({
button: 'button'
})
}
};
</script>
Related
i try to notify parallel components (rendered with v-for) about changes.
This is my markup:
<div v-for="item in items" :key="item.ID">
<my-component :item="item"></my-component>
</div>
And now, if for example something in the first instance of "my-component" changes all remaining "my-component"s should be notified about his change. How can i achieve that?
(At the moment i use this.$root.$emit and this.$root.$on)
Thanks in advance for your help.
There are a few ways you can achieve this:
1. Use props
VueJS' data-flow patterns recommend passing props down and bubbling events up. You could use this pattern by having a prop that is passed down that notifies the child components about the change.
Parent
<div v-for="item in items" :key="item.ID">
<my-component :item="item" :last-modified="lastModified"></my-component>
</div>
Child
<script>
export default {
...
watch: {
lastModified (v) {
// Do something
}
}
}
</script>
2. Use Vuex
Another way to achieve this is to use Vuex for global state management. This is the recommended option particularly if you're already using Vuex to manage state.
Parent
<div v-for="item in items" :key="item.ID">
<my-component :item="item"></my-component>
</div>
Child
<script>
import { mapGetters } from 'vuex'
export default {
...
computed: {
mapGetters([
'lastModified'
]),
...
},
watch: {
lastModified (v) {
// Do something
}
}
}
</script>
2. Use $refs
Another simple way to achieve this on a small-scale is to use ref.
Parent
<template>
<div v-for="item in items" :key="item.ID">
<my-component :item="item" :last-modified="lastModified" ref="childComponents"></my-component>
</div>
</template>
<script>
export default {
...
watch: {
lastModified () {
for (const child of this.$refs.childComponents) {
child.onModified()
}
}
}
}
</script>
Child
export default {
...
methods: {
onModified () {
// Do something
}
}
}
4. Use an event bus
This is a very handy way of achieving event propagation, but is not recommended. For this approach, we will create a third component, that is a simple vue instance that will be used for events.
bus.vue
import Vue from 'vue'
export default new Vue()
Parent
<template>
<div v-for="item in items" :key="item.ID">
<my-component :item="item"></my-component>
</div>
</template>
<script>
import Bus from 'bus'
export default {
...
watch: {
lastModified () {
Bus.$emit('modified')
}
}
}
</script>
Child
<script>
import Bus from 'bus'
export default {
...
methods: {
onModified () {
// Do something
}
},
beforeMount () {
Bus.$on('modified', this.onModified)
},
beforeDestroy () {
Bus.$off('modified', this.onModified)
}
}
</script>
I planned and made a modal and then created a button to close the modal window.
I wanted to change the value of isHomeDialog using $emit as an event of the button.
However, $emit's event was not delivered to "Home.vue"
Home.vue
<template>
<div>
<ReviewDialog
:is-open="isHomeDialog"
#close="closeEvent()/>
</div>
</template>
<script>
import ReviewDialog from '#/components/popup/dialog/ReviewDialog';
</script>
export default {
name: 'Home',
components: {
ReviewDialog
},
data () {
return {
isHomeDialog: true
};
},
methods: {
closeEvent () {
console.log('close');
isHomeDialog = false;
}
}
};
BaseDialog.vue
<template>
<div v-show="isOpen">
<div class="mask"> </div>
<div class="content">
<button
class="close"
#click="$emit('close')"> </button>
<slot/>
</div>
</div>
</template>
<script>
export default {
props: {
isOpen: {
type: Boolean,
required: true
}
}
};
Reviewdialog.vue
<template>
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'">
<div class="warp">
<p>
test
</p>
</div>
</BaseDialog>
</template>
<script>
import BaseDialog from '#/components/popup/dialog/BaseDialog';
export default {
components: {
BaseDialog
},
props: {
isOpen: {
type: Boolean,
default: false
}
}
</script>
Home
└ BaseDialog
└ ReviewDialog
In the above structure, I tried to send a request to BaseDialog and ReviewDialog with $emit, but it was not delivered to Home.
Is there any way to send $ emit to its parent component other than requesting it with $root.$emit?
BaseDialog emits the close event to its parent, ReviewDialog, which is not listening for it. So you need to listen in ReviewDialog, and re-emit the event to Home:
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'"
#close="$emit('close')"> <-- This line is new -->
Another way to do this is to have ReviewDialog pass all its listeners down:
<BaseDialog
url="#/components/popup/dialog/BaseDialog"
id="review-dialog"
:is-open="isOpen"
:header-text="'REVIEW'"
v-on="$listeners"> <-- This line is new -->
Since two-way binding is deprecated in Vue2 + child cannot mutate props directly
Likely another approach custom component with v-model
I put reference below:
vuejs update parent data from child component
Working my way learning about Vue. I chose it as the better alternative after looking at React, Angular and Svelte.
I have a simple example that its not working probably because I'm not getting/understanding the reactive behaviour of Vue.
Plain simple App:
<template>
<div id="app">
<app-header></app-header>
<router-view />
<app-footer></app-footer>
</div>
</template>
<script>
import Header from './components/Header.vue'
import Home from './components/Home.vue'
import Footer from './components/Footer.vue'
export default {
components: {
name: 'App',
'app-header': Header,
'app-footer': Footer
}
}
</script>
Where Home.vue and Footer.vue have plain HTML content on the template.
On Header.vue I have:
<template>
<div>
<h1>The Header</h1>
<nav>
<ul>
<li>Curr Player: {{ ethaccount }}</li>
<li>Prop owner: {{ propOwner }}</li>
</ul>
</nav>
<hr />
</div>
</template>
<script>
export default {
data() {
return {
ethaccount: 'N/A',
propOwner: 'N/A'
}
},
methods: {
update() {
var ethaccount = '0xAAAAAA123456789123456789123456789'
console.log('ETH Account: ' + ethaccount)
var propOwner = '0xPPPPPPPPPPP987654321987654321'
console.log('Prop Account: ' + propOwner)
}
},
mounted() {
this.update()
}
}
</script>
But I'm unable to get the header updated and unable to find what I'm doing wrong. Help.
If you need to read a little bit more about the reactivity of the datas in vuejs check this link : https://v2.vuejs.org/v2/guide/reactivity.html
If you need to access/change your data try to do it like that :
this.$data.ethaccount = 'foo';
this.$data.propOwner = 'bar';
For me the problem is taht you re-declare your variable locally by doing :
var ethaccount = "0xAA...";
By doing such you never change the value of the data you're accessing through your template.
Hope it will solve your problem.
can someone help me with the lifecycle of this?
I have 2 vue components 1. has a button (Header.vue)
and 2. is sidebar that I want to hide/show depends on value
header looks like this
<template>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a role="button" class="navbar-burger is-pulled-left" aria-label="menu" aria-expanded="false"
#click='getToggleSidebarMobile'>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
<a class="navbar-item " href="/">
<img :src="'/../images/logo.png'">
</a>
</div>
<div class="navbar-end is-pulled-right">
<div class="navbar-item">
</div>
</div>
</nav>
</template>
<script>
import {store} from '../store';
export default {
data() {
return {
hideSidebarMobile: store.state.hideSidebarMobile
}
},
methods: {
getToggleSidebarMobile(){
this.hideSidebarMobile = !this.hideSidebarMobile;
store.dispatch('getToggleSidebarMobile', this.hideSidebarMobile);
}
}
}
</script>
sidebar is bigger but trimmed version is this:
<template>
<aside class="menu " v-bind:class="{'is-hidden-touch' : store.state.hideSidebarMobile}" >
....
</aside>
</tamplate>
....
data() {
return {
sidebar: {
hideSidebarMobile: store.state.hideSidebarMobile
},
}
},
methods: {
getToggleSidebarMobile(){
store.dispatch('getToggleSidebarMobile');
}
...
update: store.js added
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
hideSidebarMobile: true
},
actions: {
getToggleSidebarMobile(context, payload){
console.log("action "+payload)
context.commit('getToggleSidebarMobile', payload)
}
},
mutations: {
getToggleSidebarMobile(state, data){
state.hideSidebarMobile = data;
console.log("Mutation "+data);
}
},
getters: {
getToggleSidebarMobile(state){
return state.hideSidebarMobile;
}
},
I also tried to use in template v-bind:class="{'is-hidden-touch' : sidebar.hideSidebarMobile}", but no luck in there as well, as I can see values are updated but I'm unable to add/remove that class where did I go wrong?
updated... forgot to upload store
The store.state.hideSidebarMobile reference in the sidebar's template is not going to work. (Unless you've set a store property on the sidebar's Vue instance equal to the Vuex store, which I'm assuming you haven't.)
If you've registered the Vuex module properly:
const store = new Vuex.Store({ ... }); // your store config
Vue.use(Vuex); // registering the plugin
new Vue({ // your root Vue instance
el: '#app',
store: store, // passing the `store` so components can reference this.$store
...
});
then you should be able to reference the store in your sidebar component via this.$store. Which also means that there is no need to import store into every file that needs to reference it.
So in your template:
v-bind:class="{'is-hidden-touch' : $store.state.hideSidebarMobile}"
I'm trying to use string interpolation to create an href inside a component v-for loop:
<template>
<div class="pa4">
<div v-for="item in navigationItems">
{{item}}
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
computed: {
...mapState({
navigationItems: state => state.navigationItems
})
}
}
</script>
Navigation items originate in the Vuex store:
export const state = {
navigationItems: ['Home', 'About', 'Blog', 'Contact']
}
Angular JS has an ng-href directive that would be perfect:
https://docs.angularjs.org/api/ng/directive/ngHref
When I use v-bind:href="item" I get 'not bound' errors. Any ideas how to pull this off?
Assuming your mapState is working it should be
<a :href="'#'+item">{{item}}</a>
Here is an example.