0
votes

I seem to recall being able to emit an event from a parent component to a child component by specifying the emitted event in the opening <template> tag of the corresponding child component. However for some reason it is no longer working in my implementation. Am I delusional or was this possibly a feature from an older version of VueJS that was deprecated, possibly in place of the event bus architecture? I am currently using Vue v2.6.12.

In my $root vue instance:

var app = new Vue({
    el: '#app',
    router,
    data: () => ({}),  
    methods: {
        clearForm() {
            this.$emit('clear-form');
        }
    }
});

Laravel layout:

<v-main>
    @yield('content')
</v-main>

Vue Router /Laravel view/vue:

@extends('layouts.user')

@section('content')
    <transition name="slide">
        <router-view></router-view>
    </transition>
@endsection

When clearForm() is called in the $root instance, shouldn't I be able to capture the emitted event in a child component like this...

<template @clear-form="clearForm"></template>

<script>
    export default {
        data() {
            
        },
        created() {
            this.$on('clearForm', clearForm);
        }, 
        methods: {
            clearForm() {
                //this is the method i want to call when emitted from $root
            }
        }
    }
</script>

UPDATE:

The answer I selected from @JoshuaAngnoe is the correct method of passing an emitted event from a $root Vue instance to a child component served through Vue-Router. However in my case, I was also leveraging the Laravel framework, which adds an additional layer of complexity because the HTML is broken up into two files (layout & view).

The Laravel View, which houses the <router-view> element, is separate from the Laravel layout which is actually the same layer as the $root Vue instance. So an additional step is required to make it work...

The declaration for the emitted event, which in the chosen answer, resides within the <router-view> element: <router-view @clear-form="clearForm" /> needs to be moved out of the <router-view> element, and into the opening template tag of the child component: <template @clear-form="clearForm">.

The structure of my initial example was correct. I was just missing $root when I called $on to capture the emitted event from the child component.

created() {
    this.$on('clearForm', clearForm);
}

Needed to be:

created() {
    this.$root.$on('clearForm', clearForm);
}
3
Parents pass stuff to children via props (a prop can be a function which can be run - basically acts as an event). Children emit events to parents. It's how it's always been, at least in 2.x. But really, you should decouple and use state. If you think Vuex is too much, give Vue.observable() a spin. With state, you decouple everything and the store is your single source of truth, in sync throughout your app. Or, should I say: anywhere it's imported.tao
I can't pass a prop directly to the child. I'm using vue-router in a laravel layout, so the child component (i.e. the current main vue server by vue-router) is not actually specified. I know i have run into this problem before, I just forgot where I specified the passed prop/event in each child component/vue. If I remember correctly it was specified in the opening template tag of each vue served by the router.McWayWeb
you can pass parent function to child <ChildComponent :function="myFunction" /> like this then parent myFunction function will bind to child function this functionKamlesh Paul
<ChildComponent :method="parentMethod" /> like thisKamlesh Paul
The problem is that the child component is not actually being specified. It is being dynamically injected via vue-router. So there never is a child component declaration such as <ChildComponent :method="parentMethod" />McWayWeb

3 Answers

1
votes

Am I delusional or was this possibly a feature from an older version of VueJS that was deprecated,

No you are not delusional, this was possible with Vue 1 if i recall correctly.

As you pointed out you are using Vue Router, it will be likely that a routed component will be responsible for triggering a clear-form event and you'd like your app to respond to that. That can be achieved by passing some listeners on <router-view></router-view>, like so <router-view @clear-form="clearForm()"></router-view>

An example:

<div id="app">
  <h1>
  Hello
  </h1>
  <router-view @clear-form="clearForm"></router-view>
</div>
Vue.use(VueRouter)
new Vue({
    el: '#app',
  router: new VueRouter({
    routes: [
        { path: '/', component: { template: `
        <div>
            Some page component
          <button @click="$emit('clear-form')">Clear form</button>
       </div>
      `} }
    ]
  }),
  
  methods: {
    clearForm() {
        alert("Clearing form")
    }
  }
});

Now the child component (route component) can send a signal to the App instance that the form should be cleared.

A working example in jsfiddle: https://jsfiddle.net/zkfu8x5s/3/

0
votes

You need to create an event listener in a lifecycle hook from your application (Created or BeforeMount or Mounted).

created() {
    this.$on('clear-form', function(){
        console.log('Event from child component emitted')
    });
 }

//or call your function

created() {
    this.$on('clear-form', clearForm) //<-- this function must be declared in your methods or actions
}
0
votes

Without using the event bus it is not possible. Although there is another way to achieve this and that is using props. The summary of that, in theory, is, pass a prop to the child component and add a watcher on that prop inside the child component. Now from a parent where you are currently emitting the event, all you need is to change the prop value and in the child component, the watcher will get executed.

Now in practicle,

var app = new Vue({
    el: '#app',
    router,
    data: () => ({
        clearForm: false,
    }),  
    methods: {
        clearForm() {
            this.clearForm = true;
        }
    }
});

Now pass this clearForm data attribute to your child component as a prop and add a watcher to listen for the change in this prop

<template></template>

<script>
    export default {
        props: ['clearForm'],
        data() {
            
        },
        created() {
          
        }, 
        methods: {
            clearFormFunc() {
                //this is the method i want to call when emitted from $root
            }
        },
        watch: {
            clearForm(value){
                if (value) {
                    this.clearFormFunc();
                }
            }
        }
    }
</script>

Update 2

You can listen on parents' events in the child component created or mounted hook by adding the following line of code in either of these hooks of the child component. Fire the event from the parent as you were firing it before, no need to change the parent.

this.$events.listen('clearForm', this.clearFormFunc);