VueJS Grandchild component call function in Great grandparent component - vue.js

Suppose I have a child component that want to send a message to a great grandparent, the code will be like this, correct me if I'm wrong:
Vue.component('child', {
template: `<div v-on:click="clicked">Click me</div>`,
methods: {
clicked: function () {
this.$emit('clicked', "hello")
},
},
});
Vue.component('parent', {
template: `<child v-on:clicked="clicked"></child>`,
methods: {
clicked: function (msg) {
this.$emit('clicked', msg)
},
},
});
Vue.component('grandparent', {
template: `<parent v-on:clicked="clicked"></parent>`,
methods: {
clicked: function (msg) {
this.$emit('clicked', msg)
},
},
});
Vue.component('greatgrandparent', {
template: `<grandparent v-on:clicked="clicked"></grandparent>`,
methods: {
clicked: function (msg) {
console.log('message from great grandchild: ' + msg);
},
},
});
Is there a possibility to directly intercept the message from the child and call the clicked function in the great-grandparent without the need to set up the passing callback at every parent?
I know I can use a custom databus, https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication, but since my components have parent-child relationship already, shouldn't I be able to notify the grandparent in a simpler way?

Not if you want to maintain encapsulation. greatgrandparent is not supposed to know about child. It knows about grandparent, but not that there are sub-components or how many. In principle, you can swap one implementation of grandparent out for another that doesn't have multiple layers. Or has even more layers to get to child. And you could put child into a top-level component.
You already know about the notion of a global event bus. A bus doesn't have to be global, though. You can pass it down the props chain. (You could use greatgrandparent itself as the bus, but that would expose it to its children; better hygiene to make a real bus.)
This distinguishes top-level components from sub-components: a sub-component will receive a bus prop to perform the functions of the top-level component that it helps to implement. A top-level component will originate the bus.
Vue.component('child', {
template: `<div v-on:click="clicked">Click me</div>`,
props: ['bus'],
methods: {
clicked: function() {
this.bus.$emit('clicked', 'hello');
},
},
});
Vue.component('parent', {
template: `<child :bus="bus"></child>`,
props: ['bus']
});
Vue.component('grandparent', {
template: `<parent :bus="bus"></parent>`,
props: ['bus']
});
Vue.component('greatgrandparent', {
template: `<grandparent :bus="bus" v-on:clicked="clicked"></grandparent>`,
data() {
return {
bus: new Vue()
};
},
created() {
this.bus.$on('clicked', this.clicked);
},
methods: {
clicked: function(msg) {
console.log('message from great grandchild: ' + msg);
},
},
});
new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<greatgrandparent id="app"></greatgrandparent>

Related

How to listen to child events created programatically in vue js?

I've two components a child and a parent.
I'm instantiating the child in the parent manually & mounting it manually.
This is because My child component is rendereless and it doesn't have a tag like <app-child></app-child> which I can use it to instantiate in the template.
NOTE THAT HERE I'M NOT BINDING TO PROPS AND LISTENING TO EVENTS IN A REGULAR MANNER AS IN THE VUE - TEMPLATE BINDING AND LISTNENING
Here I'm not dealing anything with the template.
SO I will have to pass props and listen to events as given below.
But the issue is that even though I'm emitting event from child and listening it inside the parent. I'm not seeing any sign of it.
thought I'm listening to child event in parent as given below... I'm not getting any response from the event.
this is the parent
import { Child } from "./components/child";
import store from "#/store";
export default {
name: "parent",
components: {},
props: [],
data() {
return {
child: null,
};
},
computed: {},
created() {
this.child = new Child({
store,
parent: this,
propsData: {
item: 'one' /// I'm being able to pass props and receive them in the child component
},
}).$mount();
this.child.$on("hello", (e) => console.log(e, "yes")); // this is not working.
},
mounted() {
},
methods: {},
};
this is the child emitting event... 'hello'
import Vue from "vue";
const CHILD = {
name: "child",
components: {},
props: ["item"],
data() {
return {};
},
render() {
return null;
},
computed: {},
created() {
},
mounted() {
this.$emit('hello', 'parent') /// this is child emitting event. this should be caught by the parent..
},
methods: {},
};
export const Child = Vue.extend(CHILD);
How can I solve this?
The event is firing as it should. The issue is that you're missing the event since you're registering your listener after the event already fired.
Try changing your parent component as follows:
created() {
this.child = new Child({
store,
parent: this,
propsData: {
item: 'one' /// I'm being able to pass props and receive them in the child component
},
})
this.child.$on("hello", (e) => console.log(e, "yes")); // Set up listener before event will be fired
this.child.$mount();
}
Here's an example of it working: https://codepen.io/gurupras/pen/qBNWbag

Vue watch not triggerring on elements in array

I have two components. One of them creates instances of the other one and adds them to an array then I try to watch on it to execute some functions, but it never triggers. My structure looks something like this:
const Child = Vue.extend({
data() {
triggerMe: false
},
methods: {
triggerSomething: function() {
console.log('It's called for sure');
this.triggerMe = true;
}
}
});
const Parent = Vue.extend({
data() {
children: []
},
components: {
child: Child
},
methods: {
addChild: function() {
this.children.push(new Child());
this.children[this.children.length - 1].$watch('triggerMe', _ => console.log('Never called'));
}
}
});
It works just right if it's the only one. How to fix that?
What I found is that $watch only triggers when component is mounted to some element in document. Other approach is needed:
<scipt id="parent-template">
<child
#trigger="triggerThat"
></child>
</script>
<script>
// in Child
this.$emit('trigger', someData);
// in Parent
methods: {
triggerThat: function(data) {
console.log('I can hear you');
console.log(data);
}
}
</script>

VueJs Data Passed From Root to Child Component via Prop Results in only an observable object

I have an app which calls a web service in the created() function and populates a property of the root data object. The property is passed via a prop to a child component and using the Chrome dev tools I can see that the prop data is available on the child component.
The problem I have is that I try to set data properties in the child component using values passed via the prop I end up with undefined property data. If I use the Chrome inspection tools and add a breakpoint I can see that the prop is an observable object in the form of {__ob__: Observer} and as such, I cannot directly access any of the data. My suspicion is that the child object sets it's data properties before the web service call has completed in the root.
How can I overcome this?
I've created a JsFiddle for this:
https://jsfiddle.net/ProNotion/a8c6nqsg/
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {
customerEmail: this.customer_data.customerEmail1
}
}
}
});
new Vue({
el: "#app",
data() {
return {
customer: {}
};
},
methods: {
init() {
var self = this;
axios.get("https://0bb1313e-b089-432e-b6bc-250f6162d7f0.mock.pstmn.io/GetCustomerData")
.then(response => {
self.customer = response.data;
}).catch(response => {
console.error(response);
});
}
},
created() {
this.init();
}
});
Here is my HTML markup:
<div id="app">
<mycomponent :customer_data="customer" />
</div>
<script type="x-template" id="my-component-template">
<div>
<p>{{form_data.customerEmail1}}</p>
</div>
</script>
Check response data type and format
console.log(typeof response.data) // string
{ "customerEmail1": "me#example.com", } // Remove `,`
You must parse to JSON type
axios.get(...).then(response => {
self.customer = JSON.parse(response.data.replace(',', ''))
})
Set property to watch with `deep` option
[Deep watching](https://v2.vuejs.org/v2/api/#vm-watch) will be detect nested value changes inside Objects
```
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {}
}
},
watch: {
customer_data: {
handler (val) {
this.form_data = val;
},
deep: true
}
}
});
```
Demo: https://jsfiddle.net/ghlee/f4gewvqn

How to watch router object in main vue instance

From my main vue instance I am trying to detect when a route is changed. So far what i have learned that i need to deep watch an object to detect property changes. But my approach is not working. What am i doing wrong:
const app1 = new Vue({
el: '#app1',
router,
data: {
activeRoute: router.currentRoute
},
methods: {
printclass: function() {
console.log(router.currentRoute.path);
}
},
computed: {
},
watch: {
'router.currentRoute': {
handler(newVal) {
console.log('changed');
}, deep: true
}
},
created: function() {
console.log(router.currentRoute);
}
});
I know that the currentRoute object's path property changes when the route changes(i.e; a different component is rendered).
Watch the $route.
watch: {
'$route' (to, from) {
this.yourFunction(
},

Reference component in another one

In a VueJS file i have two component :
var firstComponent = Vue.extend({
template: '#component1',
[...]
methods:
comp1function: function() {
[...]
comp2function()
}
[...]
}),
var secondComponent = Vue.extend({
template: '#component2',
[...]
methods:
comp2function: function(){
do things here
}
[...]
})
so what i want to do is to say : do comp2function when you finished to do comp1function. So is there any way i can reference a function from my template component2 in my template component1 ?
You should use Vue Events. I assume you are using VueJS 1.x?
So let's imagine you have those two components as you stated in your question:
var firstComponent = Vue.extend({
template: '#component1',
methods: {
doSomething () {
// Do things here
console.log('I do things')
// Fire an event when you want:
this.$dispatch('some-event')
}
}
})
var secondComponent = Vue.extend({
template: '#component2',
methods: {
doSomethingElse () {
console.log('I do something else')
}
},
events: {
trigger () {
this.doSomethingElse()
}
}
})
You need to trigger an event from your first component using the $dispatch method. Then, in your Vue Instance, you need to get that event, and use $broadcast to broadcast a new event to the other Vue children:
new Vue({
el: '#app',
components: {
firstComponent,
secondComponent
},
events: {
'some-event' () {
this.$broadcast('trigger')
}
}
})
You will then be able to get the event in your secondComponent and trigger whatever method you want
You can learn more about custom events on this page: http://v1.vuejs.org/guide/components.html#Parent-Child-Communication