Vue: How to call a function within a sibling component? - vue.js

I have an app that has a Parent component (app.js) and 2 child sibling components ( Modal.vue and Cart.vue).
I am getting a response back from a call within Modal.vue that needs to trigger a function within Cart.vue right after. What are the best options of going about this?
I don't currently use Vuex, which I know would make this easier.

You can use refs like so:
<parent>
<modal #triggerFunc="triggerFunc"/>
<cart ref="$cartRef"/>
</parent>
...
triggerFunc(){
this.$refs.$cartRef.functionIwantToTrigger()
}

You could create an event bus:
https://www.digitalocean.com/community/tutorials/vuejs-global-event-bus

You can use Custom Events for this using the root Vue instance of the current component tree.
In Modal.vue:
Where you are getting a response back from a call, simply emit a custom event like:
this.$root.$emit('onupdate', data);
In Cart.vue:
Simply listen to this root emitted event like
var vm = new Vue({
data: {},
mounted() {
this.$root.$on('onupdate', data => {
// trigger a function within Cart.vue
this.myFunction(data)
});
}
methods: {
myFunction: function(data) {
console.log(data);
}
}
})

Related

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 - Update Parent Data After Ajax

I'm trying out VueJS, with the aim of incrementally updating a jQuery project, but I'm having issues with props in a child component passed through data from a parent component.
My parent component is
// parent_component.js
var parentData = {};
var parentComponent = Vue.component('parentComponent', {
data: function() {
return {
parentData: _parentData
}
},
delimiters: ['((', '))']
})
$.ajax({
url: '/someUrl/',
success: function(response) {
_parentData = response
}
My Child component is:
// child_component.js
Vue.component('child-component', {
template: '<div>((data.someProp))</div>'
props: ['data'],
delimiters: ['((', '))']
})
My HTML is:
// index.html
<parent-component inline-template>
<child-component v-bind:data="parentData"></child-component>
</parent-component>
This all works fine when I update _parentData right after initializing the childComponent. But I actually need to do an Ajax call and update the _parentData, it is not updated in the childComponent.
Nb. I checked that the _parentData object is there in the callback of the Ajax call.
Also, I tried putting the Ajax call in the created option of the childComponent, but this didn't help.
What am I missing?
Update
I guess I made the classic beginner's mistake! As stated here, Vue cannot detect property addition. So I need to define the someProp property on the _parentData before making the async call.
So if I define var parentData = { someProp: '' }; it will work.
See this fiddle: https://jsfiddle.net/maaikeb/anr1e88n/
If you share the AJAX part of code, maybe I can help better, but IMHO I think that the easiest way is emiting a event from children, and catch it with the parent element when the AJAX call is done (success or error).
Events provide a way to inform your parent components of changes in children.
Template Usage:
<my-component v-on:myEvent="parentHandler"></my-component>
<!-- Or more succinctly, -->
<my-component #myEvent="parentHandler"></my-component>
Firing an Event:
...
export default {
methods: {
fireEvent() {
this.$emit('myEvent', eventValueOne, eventValueTwo);
}
}
}
Additionally, you can create global event buses to pass events anywhere in your app
Extracted from here.
Edit
Ok, I didn't understand you well, if you have problems sending data down from parent to children when updating the parent the proper way is the double data binding.
Maybe your problem is that the data is only evaluated in component creation, and your child will be created with the initial value of the parent component...
You can try some different things to solve this, like:
1 - Maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
_parentData = response
...when you should do...
_parentData.$set('someObject.someProperty', someValue)
2 - Use watch on parent instead of created:
watch:{
yourData() {
$.ajax({
url: '/someUrl/',
content_type: 'application/json',
data_type: 'json',
success: function(response) {
_parentData = response
}
})
},
3 - Try using .sync ( deprecated in 2.0 ):
<component :something.sync="variable"></component>
4 - Bind .this to the anonymous AJAX call or use arrow functions as #Bert commented:
success: () => (response) {
_parentData = response
}
Hope it helps!

Vue.js 2 pass data from component to root instance

I have a component that makes an AJAX request. In the callback function I want to pass a value back to the parent or root instance.
So my callback function for example in the component is:
function callbackFunc(vm, response){
vm.$emit('setValue', response.id);
}
and in my root instance I've tried using a method called setValue like this:
export default {
name: 'app',
data () {
return {
value : ''
}
},
methods: {
setValue: function(value){
console.log(value);
}
}
}
This doesn't work. The documentation seems to say you need to have an event inside the template for it all to get hooked up but that's not going to work in this case.
Any ideas?
Cheers!
I'm using vue-router. So there's the root element that has an App
component and then there'sthe component called Hello which has the
ajax call
In the parent component's template you will have a <router-view><\router-view> which is where the vue-router will put your child. To wire everything up, you need to add the directive to the template:
<router-view v-on:setValue="parentMethod" ><\router-view>
When the child calls $emit("setValue") after the ajax call, it will triggers parentMethod() on the parent. It's not clear why you say it won't work to hook it up in the template. Without the template, there's not really a parent/child relationship.

how can component delete itself in Vue 2.0

as title, how can I do that
from offical documentation just tell us that $delete can use argument 'object' and 'key'
but I want delete a component by itself like this
this.$delete(this)
I couldn't find instructions on completely removing a Vue instance, so here's what I wound up with:
module.exports = {
...
methods: {
close () {
// destroy the vue listeners, etc
this.$destroy();
// remove the element from the DOM
this.$el.parentNode.removeChild(this.$el);
}
}
};
Vue 3 is basically the same, but you'd use root from the context argument:
export default {
setup(props, { root }){
const close = () => {
root.$destroy();
root.$el.parentNode.removeChild(root.$el);
};
return { close };
}
}
In both Vue 2 and Vue 3 you can use the instance you created:
const instance = new Vue({ ... });
...
instance.$destroy();
instance.$el.parentNode.removeChild(instance.$el);
No, you will not be able to delete a component directly. The parent component will have to use v-if to remove the child component from the DOM.
Ref: https://v2.vuejs.org/v2/api/#v-if
Quoted from docs:
Conditionally render the element based on the truthy-ness of the expression value. The element and its contained directives / components are destroyed and re-constructed during toggles.
If the child component is created as part of some data object on parent, you will have to send an event to parent via $emit, modify (or remove) the data and the child component will go away on its own. There was another question on this recently: Delete a Vue child component
You could use the beforeDestroy method on the component and make it remove itself from the DOM.
beforeDestroy () {
this.$root.$el.parentNode.removeChild(this.$root.$el)
},
If you just need to re-render the component entirely you could bind a changing key value to the component <MyComponent v-bind:key="some.changing.falue.from.a.viewmodel"/>
So as the key value changes Vue will destroy and re-render your component.
Taken from here

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.