VUE js call a child component method in a different component hierarchy - vue.js

I have this following vuejs component hierarchy.
What i want to do it to invoke COMP_B_ONE validate() method, when COMP_A_TWO submit() method is invoked EVERY TIME.
MAIN_COMPONENT
COMP_A_ONE
COMP_B_ONE
validate()
COMP_B_TWO
validate()
COMP_A_TWO
submit()
I've already implemented an emit when submit is triggered in COMP_A_TWO which can be listened in MAIN_COMPONENT
submit() {
this.$emit('submit')
}
what seems to be the best approach in this regard? any suggestions appreciated.

I can get this done by two ways.
1 - Global EventBus
I will create an eventBus and register events on it from any file and listen it anywhere -
import { EventBus } from '#/eventBus'
// simply import it to component which need listen the event
//Register Event where you have your methods - like In your COMP_B_TWO
EventBus.$on('validate', () => { this.validate() })
// Emit event from another component
EventBus.$emit('validate')// Like directly from your COMP_A_TWO
To know how to create a eventBus follow this - Global Event Bus Vue
Another way I can think is
2 - Refs
Add reference to COMP_A_ONE like
<COMP_A_ONE ref = "one" />
Then add reference to COMP_B_ONE
<COMP_B_ONE ref = "b-one" />
Now when you trigger submit from main component
execute it -
this.$on('submit', () => {
this.$refs.one['b-one'].validate()
})
It totally depends which way you wanna go -
If you need to call validate for many more places, I would suggest choosing EventBus
You just need current component to have it, use Refs

Related

Programatically assign handler for native event in Vue JS?

I am trying to leverage a Vue mixin to add behavior when a native event happens. Using a mixin will allow me to share that across several components. Specifically, when a field component (or button, or checkbox, etc.) has focus, and the Escape key is pressed, the field loses focus.
A similar Stack Overflow question seemed to indicate I could listen for native events (see code comment about multiple events).
However, the Vue Documentation for programmatically adding an event listener using $on says that it will
Listen for a custom event on the current vm...
(Emphasis added)
Unsure if the custom event remark is absolute or based on the context, I have been experimenting. I have been trying to listen for the native keyup event (using the Vue alias keyup.esc) but have had no success. So I am wondering if it is indeed limited to custom events, and if so, why?
You can see my experiment in a code sandbox. The custom event works, the native does not.
The mixin looks like so:
// escape.mixin.js
export default {
created() {
// Custom event
this.$on("custom-event", function() {
console.log("Custom event handled by mixin");
});
// Native event
this.$on(["keyup.esc", "click"], function() {
alert("Native event handled!");
});
}
};
The main point of all this is to be able to add the behavior to a set of components by adding to how the event is handled, without overriding behavior that might also exist on the component. The secondary goal is to provide the behavior by simply adding the mixin, and not having to do component level wiring of events.
So a component script would look something like this:
// VText component
import escapeMixin from "./escape.mixin";
export default {
name: "VText",
mixins: [escapeMixin],
methods: {
onFocus() {
console.log("Has Focus");
this.$emit("custom-event");
}
}
};
Also, I was trying to avoid attaching the listener to the <input> element directly with vanilla JS because the Vue documentation suggested that letting Vue handle this was a good idea:
[When using v-on...] When a ViewModel is destroyed, all event listeners are automatically removed. You don’t need to worry about cleaning it up yourself.
Solution
skirtle's solution in the comment below did the trick. You can see it working in a code sandbox.
Or here's the relevant mixin:
export default {
mounted() {
this.$el.addEventListener("keyup", escapeBlur);
},
beforeDestroy() {
this.$el.removeEventListener("keyup", escapeBlur);
}
};
function escapeBlur(e) {
if (e.keyCode === 27) {
e.target.blur();
console.log("Lost focus");
}
}

How do you and should you move event bus functionality to Vuex store?

Right now I'm using an event bus to call methods of certain Vue components from other non-related Vue components.
I have a functioning Vuex store, so I'm trying to get rid of the event bus and move this functionality to Vuex store.
Questions
Should I move event bus functionality to Vuex store or should I use both?
What's the best way to implement event bus functionality in a Vuex store?
Could you please give an actual example of how to call a method inside another non-related component using Vuex:
First.vue
methods: {
test1 () {
console.log('test1 was called')
}
}
Second.vue
methods: {
callMethodInsideFirstComponent () {
...
}
}
If you have a need for an event bus, there's no harm in using one. The main problems people have with them are 1) that they pollute the global event space (obviously), 2) if relied upon too heavily can become cumbersome to track, and 3) risk collisions with event names or unintended side effects.
Vuex is a shared reactive state accessible anywhere throughout your application. Key word being reactive, don't think you will be calling methods between components, i.e. component A calls a method defined in component B. Instead, component A will mutate a given property in the state tree which component B is observing (typically in a computed property or watcher).
For example:
// First.vue
<template>
<div>{{ myStoreProp }}</div>
</template>
...
computed: {
myStoreProp () {
return this.$store.getters['myModule/myStoreProp']
}
}
// Second.vue
<template>
<button #click="updateMyStoreProp('Hello from Second.vue')">Click Me</button>
</template>
...
methods: {
updateMyStoreProp (value) {
this.$store.commit('myModule/myStoreProp', value)
}
}
Now whenever Second.vue calls it's updateMyStoreProp function, the value committed to the store will reflect in First.vue's template, in this case printing "Hello from Second.vue".
You can use event bus to emit and listen event, but you should use vuex when your app become complex. Use vuex API subscribe to implement event bus functionality using vuex. https://vuex.vuejs.org/api/#subscribe
Just simple step, you commit mutation in component A, and use subscribe to listen mutation in component B.

Calling method of another router-view's component / Vue.js

I have two <router-view/>s: main and sidebar. Each of them is supplied with a component (EditorMain.vue and EditorSidebar.vue).
EditorMain has a method exportData(). I want to call this method from EditorSidebar on button click.
What is a good way of tackling it?
I do use vuex, but i don't wanna keep this data reactive since the method requires too much computational power.
I could use global events bus, but it doesn't feel right to use it together with vuex (right?)
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
Is there any good practice for this? Or did i make some mistakes with the way app is set up? Did is overlooked something in documentation?
After two more years of my adventure with Vue I feel confident enough to answer my own question. It boils down to communication between router views. I've presented two possible solutions, I'll address them separately:
Events bus
Use global events bus (but it doesn't feel right to use it together with vuex)
Well, it may not feel right and it is surely not a first thing you have to think about, but it is perfectly fine use-case for event-bus. The advantage of this solution would be that the components are coupled only by the event name.
Router-view event listeners
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
This way of solving this problem is also fine, buy it couples components together. Coupling happens in the component containing <router-view/> where all the listeners are set.
Big number of listeners could be addressed by passing an object with event: handler mapping pairs to v-on directive; like so:
<router-view v-on="listeners"/>
...
data () {
return {
listeners: {
'event-one': () => console.log('Event one fired!'),
'event-two': () => console.log('The second event works as well!')
}
}
You could create a plugin for handling exports:
import Vue from 'vue'
ExportPlugin.install = function (Vue, options) {
const _data = new Map()
Object.defineProperty(Vue.prototype, '$exporter', {
value: {
setData: (svg) => {
_data.set('svg', svg)
},
exportData: () => {
const svg = _data.get('svg')
// do data export...
}
}
})
}
Vue.use(ExportPlugin)
Using like:
// EditorMain component
methods: {
setData (data) {
this.$exporter.setData(data)
}
}
// EditorSidebar
<button #click="$exporter.exportData">Export</button>

VueJS 2 : Watch method trigger in a component

I'm working on VueJS2 with 2 components.
Desired behaviour: Each time a method is triggered in one component, i would like to trigger a method in the other component.
I assume watch and $refs are what I need. this is what it looks like :
watch: {
'this.$refs.component1.method1': () => {
console.log('TRIGGERED method1')
this.$refs.component2.method1()
},
'this.$refs.component1.method2': () => {
console.log('TRIGGERED metdod2')
this.$refs.component2.method2()
}
Unfortunately, this doesn't work. Is it possible to watch a method call?
Typically watchers and refs are not used in this scenario. What you can use depends a bit how the components are organized. If you want to watch a method from child to parent you can simply listen to a custom event in the component. That way you would emit the event from the component using $emit(customevent). You can then add the listener in the parent component using #customevent="yourMethod".
The vue docs explain that very nicely:
https://v2.vuejs.org/v2/guide/components-custom-events.html
When they do not have a parent child relationship the event bus is what you need. This typically means that you create a .js file called eventbus.js or something like that containing this:
import Vue from 'vue';
export const EventBus = new Vue();
You can then import your eventbus.js in every component where you want to exchange events and then emit events to the global evenbus like this:
import { EventBus } from './event-bus.js';
export default {
methods: {
EmitmyMethod () {
EventBus.$emit('customevent')
},
ListenToMyMethod () {
EventBus.$on('customevent')
}
}
}
More info about that here:
https://alligator.io/vuejs/global-event-bus/

VueJS access child component's data from parent

I'm using the vue-cli scaffold for webpack
My Vue component structure/heirarchy currently looks like the following:
App
PDF Template
Background
Dynamic Template Image
Static Template Image
Markdown
At the app level, I want a vuejs component method that can aggregate all of the child component's data into a single JSON object that can be sent off to the server.
Is there a way to access child component's data? Specifically, multiple layers deep?
If not, what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values? I'm trying to avoid hard dependencies between components, so as of right now, the only thing passed using component attributes are initialization values.
UPDATE:
Solid answers. Resources I found helpful after reviewing both answers:
Vuex and when to use it
Vuex alternative solution for smaller apps
In my child component, there are no buttons to emit changed data. It's a form with somewhat 5~10 inputs. the data will be submitted once you click the process button in another component. so, I can't emit every property when it's changing.
So, what I did,
In my parent component, I can access child's data from "ref"
e.g
<markdown ref="markdowndetails"></markdown>
<app-button #submit="process"></app-button>
// js
methods:{
process: function(){
// items is defined object inside data()
var markdowns = this.$refs.markdowndetails.items
}
}
Note: If you do this all over the application I suggest move to vuex instead.
For this kind of structure It's good to have some kind of Store.
VueJS provide solution for that, and It's called Vuex.If you are not ready to go with Vuex, you can create your own simple store.
Let's try with this
MarkdownStore.js
export default {
data: {
items: []
},
// Methods that you need, for e.g fetching data from server etc.
fetchData() {
// fetch logic
}
}
And now you can use those data everywhere, with importing this Store file
HomeView.vue
import MarkdownStore from '../stores/MarkdownStore'
export default {
data() {
sharedItems: MarkdownStore.data
},
created() {
MarkdownStore.fetchData()
}
}
So that's the basic flow that you could use, If you dont' want to go with Vuex.
what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values?
The flow of props is one way down, a child should never modify its props directly.
For a complex application, vuex is the solution, but for a simple case vuex is an overkill. Just like what #Belmin said, you can even use a plain JavaScript object for that, thanks to the reactivity system.
Another solution is using events. Vue has already implemented the EventEmitter interface, a child can use this.$emit('eventName', data) to communicate with its parent.
The parent will listen on the event like this: (#update is the shorthand of v-on:update)
<child :value="value" #update="onChildUpdate" />
and update the data in the event handler:
methods: {
onChildUpdate (newValue) {
this.value = newValue
}
}
Here is a simple example of custom events in Vue:
http://codepen.io/CodinCat/pen/ZBELjm?editors=1010
This is just parent-child communication, if a component needs to talk to its siblings, then you will need a global event bus, in Vue.js, you can just use an empty Vue instance:
const bus = new Vue()
// In component A
bus.$on('somethingUpdated', data => { ... })
// In component B
bus.$emit('somethingUpdated', newData)
you can meke ref to child component and use it as this
this.$refs.refComponentName.$data
parent-component
<template>
<section>
<childComponent ref="nameOfRef" />
</section>
</template>
methods: {
save() {
let Data = this.$refs.nameOfRef.$data;
}
},
In my case I have a registration form that I've broken down into components.
As suggested above I used $refs, In my parent I have for example:
In Template:
<Personal ref="personal" />
Script - Parent Component
export default {
components: {
Personal,
Employment
},
data() {
return {
personal: null,
education: null
}
},
mounted: function(){
this.personal = this.$refs.personal.model
this.education = this.$refs.education.model
}
}
This works well as the data is reactive.