Let 2 single file components communicate with each other - javascript

How can I let 2 single file components communicate with each other.
For example: I have 2 file components. Content.vue and a Aside.vue
How can i create something like, when I click on a button inside Aside.vue that something will update inside Content.vue
this is how the 2 single file compontents look inside the index.html:
<div class="container articleContainer">
<article-content></article-content>
<article-aside></article-aside>
</div>
Aside.vue:
<template>
<aside>
<span #click="updateCounter">Dit is een aside.</span>
</aside>
</template>
<script>
export default {
data() {
return {
aside: "aside message"
}
}
}
</script>
Content.vue
<template>
<article>
<p>{{ counter }}</p>
<button #click="updateCounter">Update Counter</button>
</article>
</template>
<script>
export default {
data() {
return {
counter: 0
}
}
methods: {
updateCounter: function() {
this.counter = this.counter + 2;
},
}
}
</script>
When I click on the span inside the Aside template how can I use updateCounter to update the counter inside Content.vue.

if your app is not aas big or complex to use vuex , you can set up an EventBus like this:
export const EventBus = new Vue();// in your main.js file
in Aside.vue:
<template>
<aside>
<span #click="updateCounter">Dit is een aside.</span>
</aside>
</template>
<script>
import {EventBus} from './path/to/main.js'
export default {
data() {
return {
aside: "aside message"
}
},
methods:{
updateCounter(){
EventBus.emit('updateCounter');
}
}
}
</script>
in Content.vue
<template>
<article>
<p>{{ counter }}</p>
<button #click="updateCounter">Update Counter</button>
</article>
</template>
<script>
import {EventBus} from './path/to/main.js'
export default {
data() {
return {
counter: 0
}
}
created() {
EventBus.on('updateCounter', () => {
this.counter = this.counter + 2;
});
},
methods: {
updateCounter: function() {
this.counter = this.counter + 2;
},
}
}
</script>

Option 1: Have a value in the App.vue that gets reflected by both the components. (That's the this.$parent.someParentMethod(someValue);-way, which would be mixed with props).
Option 2 (way easier, cleaner and best-practice): vuex

Communication between any components using Event Bus
Event Bus is not limited to a parent-child relation. You can share information between any components.
<script>
export default
name: 'ComponentA',
methods: {
sendGlobalMessage() {
this.$root.$emit('message_from_a', arg1, arg2);
}
}
}
</script>
In the above ComponentA, we are firing an event “message_from_a” and passing arguments. Arguments are optional here. Any other component can listen to this event.
<script>
export default
name: 'ComponentB',
mounted() {
this.$root.$on('message_from_a', (arg1, arg2) => {
console.log('Message received');
});
}
}
</script>
In ComponentB to listen an event, we have to register it first. We can do so by putting an event listener inside mounted() callback. This callback will be triggered when an event is fired from any component.
source

Related

Active events in vue instance encapsulated inside shadowDOM

I'm searching to trigger event from a vue instance which is encapsulated inside a shadowDOM.
For the moment, my code create a new Vue instance inside a shadowDOM and print the template without the style (because nuxt load the style inside the base vue instance).
I can call methods of each instance
But, when i'm trying to add an event / listener on my component, it doesn't work.
DOM.vue
<template>
<section ref='shadow'></section>
</template>
<script>
import bloc from '#/components/bloc.vue'
import Vue from 'vue'
export default {
data() {
return {
vm: {},
shadowRoot: {},
isShadowReady: false
}
},
watch: {
isShadowReady: function() {
let self = this
this.vm = new Vue({
el: self.shadowRoot,
components: { bloc },
template: "<bloc #click='listenClick'/>",
methods: {
listenClick() { console.log("I clicked !") },
callChild() { console.log("I'm the child !") }
},
created() {
this.callChild()
self.callParent()
}
})
}
},
mounted() {
const shadowDOM = this.$refs.shadow.attachShadow({ mode: 'open' })
this.shadowRoot = document.createElement('main')
shadowDOM.appendChild(this.shadowRoot)
this.isShadowReady = true
},
methods: {
callParent() {
console.log("I'am the parent")
},
}
}
</script>
bloc.vue
<template>
<div>
<p v-for='word in words' style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
}
}
</script>
Any idea ?
Thank you
Hi so after i watched at your code i think it's better to use the $emit to pass event from the child to the parent and i think it's more optimized then a #click.native
template: "<bloc #someevent='listenClick'/>",
<template>
<div #click="listenClickChild">
<p v-for='(word, idx) in words' :key="idx" style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
},
methods: {
listenClickChild() {
this.$emit('someevent', 'someValue')
}
}
}
</script>
bloc #click.native='listenClick'/>

Do custom events propagate up the component chain in Vue 3?

Custom events didn't propagate in Vue 2. Is there a change in Vue 3 because, as the following example shows, it looks like custom events bubble up the component chain:
const Comp1 = {
template: `
<button #click="this.$emit('my-event')">click me</button>
`
}
const Comp2 = {
components: {
Comp1
},
template: `
<Comp1/>
`
}
const HelloVueApp = {
components: {
Comp2
},
methods: {
log() {
console.log("event handled");
}
}
}
Vue.createApp(HelloVueApp).mount('#hello-vue')
<script src="https://unpkg.com/vue#next"></script>
<div id="hello-vue" class="demo">
<Comp2 #my-event="log"/>
</div>
Yes, by default they now fall through in VUE v3
You need to define inheritAttrs: false to prevent that
link to docs, though it doesn't seem to indicate that it affects the events too. It just mentions attributes, but events are part of attributes($attrs).
const Comp1 = {
template: `
<button #click="this.$emit('my-event')">click me</button>
`
}
const Comp2 = {
components: {
Comp1
},
template: `
<Comp1/>
`,
inheritAttrs: false
}
const HelloVueApp = {
components: {
Comp2
},
methods: {
log() {
console.log("event handled APP");
}
}
}
Vue.createApp(HelloVueApp).mount('#hello-vue')
<script src="https://unpkg.com/vue#next"></script>
<div id="hello-vue" class="demo">
<Comp2 #my-event="log"/>
</div>
You have to handle the event into Comp2 :
<Comp1 #my-event="this.$emit('my-event')"/>

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

How to test if a component emits an event in Vue?

I have two components. Child component emits an 'input' event when it's value changed and parent component takes this value with v-model. I'm testing ChildComponent. I need to write a test with Vue-test-utils to verify it works.
ParentComponent.vue:
<template>
<div>
<child-component v-model="search"></child-component>
<other-component></other-component>
...
</div>
</template>
ChildComponent.vue:
<template>
<input :value="value" #change="notifyChange($event.target.value)"></input>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
#Component
export default class ChildComponent extends Vue {
#Prop({ default: '' }) readonly value!: string
notifyChange(value: string) {
this.$emit('input', value)
}
}
</script>
child-component.spec.ts:
describe('ChildComponent', () => {
let wrapper: any
before(() => {
wrapper = VueTestUtils.shallowMount(ChildComponent, {})
})
it(`Should emit 'input' event when value change`, () => {
const rootWrapper = VueTestUtils.shallowMount(ParentComponent)
wrapper.vm.value = 'Value'
wrapper.findAll('input').at(0).trigger('change')
assert.isTrue(!!rootWrapper.vm.search)
})
})
I didn't write the exact same code but the logic is like this.
My components work properly. 'child-component.spec.ts' doesn't work. I need to write a test for it.
TESTED.
This is a simple way of testing an emit, if someone is looking for this.
in your test describe write this.
describe('Close Button method', () => {
it('emits return false if button is clicked', (done) => {
wrapper.find('button').trigger('click')
wrapper.vm.$nextTick(() => {
wrapper.vm.closeModal() //closeModal is my method
expect(wrapper.emitted().input[0]).toEqual([false]) //test if it changes
done()
})
})
})
my vue comp
<div v-if="closeButton == true">
<button
#click="closeModal()"
>
...
</button>
</div>
my props in vue comp
props: {
value: {
type: Boolean,
default: false
},
my methods in vue comp
methods: {
closeModal() {
this.$emit('input', !this.value)
}
}
Here's an example that will help you:
Child Component.vue
<template>
<div>
<h2>{{ numbers }}</h2>
<input v-model="number" type="number" />
<button #click="$emit('number-added', Number(number))">
Add new number
</button>
</div>
</template>
<script>
export default {
name: "ChildComponent",
props: {
numbers: Array
},
data() {
return {
number: 0
};
}
};
</script>
Parent Component.vue
<template>
<div>
<ChildComponent
:numbers="numbers"
#number-added="numbers.push($event)"
/>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
name: "ParentComponent",
data() {
return {
numbers: [1, 2, 3]
};
},
components: {
ChildComponent
}
};
</script>
Follow this article: https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87
I hope this will help you.
in your parent component listen to the emitted event "input"
<template>
<div>
<child-component #input="get_input_value" v-model="search"></child-component>
<other-component></other-component>
...
</div>
</template>
and in your script add method get_input_value()
<script>
...
methods:
get_input_value(value){
console.log(value)
}
</script>

Vue async component callback

<template>
<div>
<AsyncComponent1></AsyncComponent1>
<!-- Render AsyncComponent2 after AsyncComponent1 -->
<AsyncComponent2></AsyncComponent2>
</div>
</template>
<script>
export default {
name: "My Component"
components: {
AsyncComponent1: () => import("./AsyncComponent1"),
AsyncComponent2: () => import("./AsyncComponent2")
}
};
</script>
I'm loading two components asynchronously within a component but I need one of the components to render after the other. I wonder if thats possible?
Add a v-if to a ref in the other component.
<template>
<div>
<AsyncComponent1 ref="c1"></AsyncComponent1>
<!-- Render AsyncComponent2 after AsyncComponent1 -->
<AsyncComponent2 v-if="$refs.c1"></AsyncComponent2>
</div>
</template>
You could have the first component emit an event, that is listened to by the parent and used to toggle the second component
<template>
<div>
<AsyncComponent1 v-on:loaded="componentLoaded"></AsyncComponent1>
<!-- Render AsyncComponent2 after AsyncComponent1 -->
<AsyncComponent2 v-if="hasComponent"></AsyncComponent2>
</div>
</template>
<script>
export default {
name: "My Component",
components: {
AsyncComponent1: () => import("./AsyncComponent1"),
AsyncComponent2: () => import("./AsyncComponent2")
},
data: {
hasComponent: false
},
methods: {
componentLoaded() {
this.hasComponent = true;
}
}
};
</script>
And then in AsyncComponent1.vue:
...
mounted() {
this.$emit("loaded");
}
...

Categories

Resources