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>
Related
I have the following setup for my vue application
var store = {
...
state: {
currentCustomer:{},
},
};
current customer has a property that is an object called payment method
app:
var app= new Vue({
el:'#application',
data: {
sharedState: store.state
}
});
and a couple of components:
Vue.component('user_search', {
template: '#user_search-template',
data() {
return {
sharedState: store.state
}
},
methods: {
getCustomerData: function () {
this.sharedState.currentCustomer(c);
}
mounted: function () {
...
}
});
and
Vue.component('paymentdetails',{
template: '#payment_details_template',
data(){
return{
sharedState: store.state
}
},
mounted:function(){
...
}});
The issue is like this. The payment method component does not bind to the payment details object that is nested inside the current customer object
any suggestions?
Yeah, I think what you are looking for is a computed property for accessing the data.
Vue.component('paymentdetails',{
template: '#payment_details_template',
computed{
sharedState() {
return store.state
}
},
mounted:function(){
...
}});
Maybe give that a try and see how it works.
I have a child component that's basically a search box. When the user types something and presses enter, an event is fired that goes to the parent with the search topic:
export default {
name: "SearchBar",
methods: {
searchRequested(event) {
const topic = event.target.value;
this.$emit('searchRequested', topic);
}
}
};
The parent receives the event and updates a prop connected to other of its children (an image gallery):
<template>
<div id="app">
<SearchBar #searchRequested="onSearchRequested($event)" />
<Images :topic="topic" />
</div>
</template>
<script>
import SearchBar from './components/SearchBar.vue'
import Images from './components/Images.vue'
export default {
name: 'app',
components: {
SearchBar,
Images
},
data() {
return {
topic: ''
};
},
methods: {
onSearchRequested(topic) {
this.topic = topic;
}
}
}
</script>
So far, so good. But now I want the child component load itself with images related to the searched topic whenever the user performs a new search. For that, the child component Images must be aware of a change on its property topic, so I created a computed one:
import { ImagesService } from '../services/images.service.js';
export default {
data() {
return {
topic_: ''
};
},
methods: {
updateImages() {
const images = new ImagesService();
images.getImages(this.topic_).then(rawImages => console.log(rawImages));
}
},
computed: {
topic: {
get: function() {
return this.topic_;
},
set: function(topic) {
this.topic_ = topic;
this.updateImages();
}
}
}
};
But unfortunately, the setter never gets called. I have to say I'm new in Vue, so probably I'm doing something wrong. Any help will be appreciated.
You don't need to create computed in the main component. Images component is already aware of the changes in the topic prop.
You need to watch the changes of topic and do an async operation in 'Images.vue'. It's possible with Vue's watchers.
Vue docs watchers
'./components/Images.vue'
<template>...</template>
<script>
export defult {
props: ['topic'],
data(){
return {
images: []
}
},
watch: {
topic(newVal){
// do async opreation and update data.
// ImageSerice.get(newVal)
// .then(images => this.images = images)
}
}
}
</script>
I have created one of component in vuejs something like this
var tree_data = Vue.extend({
template: '#tree_data_template',
props: [
'groupmodal',
'search-name',
'tree-id'
], // props in single quotes
data: function () {
return {
shared: d2d.store
};
}
});
And use this component in another template like this.
var template_data = Vue.extend({
template: '#template_data',
created: function () {
var self = this;
self.shared.setCurrentRoute('templates');
},
components: {
'tree-data': tree_data
},
data: function () {
return {
id: this.$route.params.id,
shared: d2d.store,
};
},
methods: {
destroyComponent: function () {
//Need to code for destroy tree-data component
}
}
});
Blade file code
<tree-data
groupmodal="false"
search-name="user_search"
tree-id="user_tree"
>
</tree-data>
So finally how can i destroy my "tree-data" component through the "destroyComponent()" method
As cobaltway said you can use v-if
Setting v-if initially to false will render(generate) the component.
Then in your method setting v-if to true will destroy the component.
html
<div id="template_data">
<tree-data v-if="destroyComponent"></tree-data>
</div>
script
var template_data = Vue.extend({
template: '#template_data',
created: function () {
var self = this;
self.shared.setCurrentRoute('templates');
},
components: {
'tree-data': tree_data
},
data: function () {
return {
id: this.$route.params.id,
shared: d2d.store,
destroyComponent:true
};
},
methods: {
destroyComponent: function () {
//Need to code for destroy tree-data component
this.destroyComponent = false;
}
}
});
Here is the fiddle
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
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>