vuejs emit from a component of a component to the parent - vuejs2

I'm using a component2 in my component1. I want to emit a parent function from component2 but only component1 is called in parent.
At the moment I'm emit in component2 and listening in component1. Then emit in component1 again and listening in parent.
My current code:
// Emit the toggle-filter in component2
Vue.component('component2', {
template: `<div #click="$emit('toggle-filter')"></div>`
});
// Capture it in component1, the emit again
Vue.component('component1', {
template: `<div>
<component2 #toggle-filter="toggleFilter"></component2>
</div>`,
methods: {
toggleFilter () {
this.$emit('toggle-filter');
},
}
});
// At last, capture it in parent
new Vue({
el: '#filters',
template: `<div id="filters">
<component1 #toggle-filter="toggleFilter"></component1>
</div>`,
methods: {
toggleFilter () {
console.log('filter toggled');
}
}
});
What I want to achieve is something like below.
// Emit toggle-filter in component2
Vue.component('component2', {
template: `<div #click="$emit('toggle-filter')"></div>`
});
Vue.component('component1', {
template: `<div>
<component2></component2>
</div>`
});
// Then capture it in parent
new Vue({
el: '#filters',
template: `<div id="filters">
<component1 #toggle-filter="toggleFilter"></component1>
</div>`,
methods: {
toggleFilter () {
console.log('filter toggled');
}
}
});

Starting from Vue 2, methods related to cross-component communication has been removed, and it's recommended to centralise data and manage shared state using Vuex. If the example pattern occurs a lot in your app, consider to use Vuex instead.
Vue still allows to create an event hub and let components communicate through the hub globally. But remember that it's considered anti-pattern.
var eventHub = new Vue()
Vue.component('component2', {
template: `<div #click="toggleFilter"></div>`,
methods: {
toggleFilter() {
eventHub.$emit('toggle-filter')
}
}
});
Vue.component('component1', {
template: `<div><component2 ></component2></div>`
}
new Vue({
el: '#filters',
template: `<div id="filters"><component1></component1></div>`,
created() {
eventHub.$on('toggle-filter', this.toggleFilter)
},
beforeDestroy() {
eventHub.$off('toggle-filter', this.toggleFilter)
},
methods: {
toggleFilter () {
console.log('filter toggled');
}
}
});

Related

How to dynamically change components in VueJS

I want to build an app in vue.js where you can dynamically switch between app views without page reloading.
What is the best way to do it?
And why this code doesn't work? Thank you!
var View01 = {
template: `<button #click="swapComponent('view-02')" type="submit">NEXT VIEW</button>`
}
var View02 = {
template: `<button #click="swapComponent('view-01')" type="submit">BACK TO PREVIOUS VIEW</button>`
}
var vm = new Vue({
el: '#app',
data: {
currentComponent: 'view-01'
},
components: {
'view-01': View01,
'view-02': View02,
},
methods: {
swapComponent: function(component) {
this.currentComponent = component;
}
}
})
You're trying to call a parent method from the child components. You need to pass the method as a prop so it is in the child's scope.
var View01 = {
template: `<button #click="swapComponent('view-02')" type="submit">NEXT VIEW</button>`,
props: ['swapComponent']
}
var View02 = {
template: `<button #click="swapComponent('view-01')" type="submit">BACK TO PREVIOUS VIEW</button>`,
props: ['swapComponent']
}
var vm = new Vue({
el: '#app',
data: {
currentComponent: 'view-01'
},
components: {
'view-01': View01,
'view-02': View02,
},
methods: {
swapComponent: function(component) {
this.currentComponent = component;
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<div :is="currentComponent" :swap-component="swapComponent"></div>
</div>

VueJs Nested props coming through undefined

I am trying to access an array which is part of a prop (event) passed into a component, but when in created() or mounted() the array part of the event prop (the rest is fine) comes through as undefined.
As can be seen below, when I inspect the props in the vue chrome plugin, the registration_fields are there.
I can add a watcher to the event prop and can access the registration_fields that way, but this seems very awkward to have to do this to access already passed in data.
This is from the Chrome vue inspector:
event:Object
address1_field:"Some Address 1"
address2_field:"Some Address 2"
approved:true
registration_fields:Array[1]
This is what part of my vue file looks like:
export default {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
},
watch: {
event() {
this.regFields = this.event.registration_fields //Can access it here
});
}
}
}
I am using Vue 2.4.4
This is how the component is called:
<template>
<tickets v-if="event" :event="event"></tickets>
</template>
<script>
import tickets from './main_booking/tickets.vue'
export default {
created() {
var self = this;
this.$http.get('events/123').then(response => {
self.event = response.data
}).catch(e => {
alert('Error here!');
})
},
data: function () {
return {event: {}}
},
components: {
tickets: tickets
}
}
</script>
Thank you
It actually works fine without the watcher.
new Vue({
el: '#app',
data: {
event: undefined
},
components: {
subC: {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
}
}
},
mounted() {
setTimeout(() => {
this.event = {
registration_fields: [1, 3]
};
}, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c v-if="event" :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>
If, as Belmin Bedak suggests in the comment below, event is populated asynchronously, it comes in as undefined because it's undefined. In that case, you need a watcher, or, somewhat more elegantly, use a computed:
new Vue({
el: '#app',
data: {
event: {}
},
components: {
subC: {
props: ['event'],
computed: {
regFields() {
return this.event.registration_fields;
}
}
}
},
// delay proper population
mounted() {
setTimeout(() => { this.event = {registration_fields: [1,2,3]}; }, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>

How to pass property to component used in main.js from other components in vuejs

Basically I want to a loadingbar component globally (included in app template)
Here is my loadingbar component
<template>
<div class="loadingbar" v-if="isLoading">
Loading ...
</div>
</template>
<script>
export default {
name: 'loadingbar',
props: ['isLoading'],
data () {
return {
}
}
}
</script>
<style scoped>
</style>
and in main.js, I have included this component as
import LoadingBar from './components/LoadingBar.vue';
new Vue({
router,
data () {
return {
isLoading: true
};
},
methods: {
},
created: function () {
},
components: {
LoadingBar
},
template: `
<div id="app">
<LoadingBar :isLoading="isLoading"/>
<router-view></router-view>
</div>
`
}).$mount('#app');
My aim is to show loading component based upon the value of variable isLoading. The above code working fine. But I want to use set isLoading variable from other component (so that to decide whether to show loading component). Eg. In post components
<template>
<div class="post container">
</div>
</template>
<script>
export default {
name: 'post',
data () {
return {
posts: []
}
},
methods: {
fetchPosts: function() {
// to show loading bar
this.isLoading = true;
this.$http.get(APIURL+'listpost')
.then(function(response) {
// to hide loading bar
this.isLoading = false;
console.log("content loaded");
});
}
},
created: function() {
this.fetchPosts();
}
}
</script>
<style scoped>
</style>
Of coarse we can't access isLoading directly from main.js so i decided to use Mixin so i put following code in main.js
Vue.mixin({
data: function () {
return {
isLoading: false
};
}
});
This however allow me to access isLoading from any other component but I can't modify this variable. Can any help me to achieve this?
Note: I know i can achieve this by including loadingbar in individual component (I tried that and it was working fine, But i do not want to do that as loadingbar is needed in every component so i was including in main template/component)
You could use Vuex like so:
// main.js
import Vuex from 'vuex'
let store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
SET_IS_LOADING(state, value) {
state.isLoading = value;
}
},
getters: {
isLoading(state) {
return state.isLoading;
}
}
})
import LoadingBar from './components/LoadingBar.vue';
new Vue({
router,
store, // notice you need to add the `store` var here
components: {
LoadingBar
},
template: `
<div id="app">
<LoadingBar :isLoading="$store.getters.isLoading"/>
<router-view></router-view>
</div>
`
}).$mount('#app');
// script of any child component
methods: {
fetchPosts: function() {
// to show loading bar
this.$store.commit('SET_IS_LOADING', true);
this.$http.get(APIURL+'listpost')
.then(function(response) {
// to hide loading bar
this.$store.commit('SET_IS_LOADING', false);
console.log("content loaded");
});
}
},

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

Vue.js parent child communication using props

I have the setup below using vue2.0. The method something() is called which updates 'place' on the parent. I want the child to watch for changes to place, and when it updates to react accordingly. However, the watch method in the child is never called. Any idea what's wrong here please?
Thanks,
// parent
import home from './components/home.vue'
var App = window.App = new Vue({
data: {
place: '',
},
methods: {
something: function(event) {
this.place = 'some new place'
})
},
components: {
'home': home,
}
}).$mount('#app')
// child ('./components/home.vue')
export default {
props: ['place'],
// need to react here when place changes
watch: {
place: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
}
Do you pass the place prop to the component?
Here is a working snippet:
var home = {
props: ['place'],
template: '<div>home: {{ place }}</div>',
watch: {
place: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
}
};
var App = new Vue({
el: '#app',
components: {
'home': home
},
data: {
place: 'somewhere'
},
methods: {
something: function(event) {
this.place = 'some new place'
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<button #click="something">set place</button>
<p>app: {{ place }}</p>
<home :place="place"></home>
</div>