Rafael de Azeredo
Rafael de Azeredo

Reputation: 453

Pass event from parent to child

I have an AppRoot.vue, AppToolbar.vue and AppDrawer.vue

I need to change the isDrawerOpen variable of the AppDrawer.vue when I click a button in AppToolbar.vue, and are declared in AppRoot.vue.

How can I do this? Can anyone help me?

AppRoot.vue

<template>
    <v-app id="app-root">
        <app-drawer></app-drawer>
        <app-toolbar :title="title"></app-toolbar>
        <v-content>
            <v-container>
                <slot><h1>Não há nada para exibir aqui!</h1></slot>
            </v-container>
        </v-content>
        <app-footer></app-footer>
    </v-app>
</template>

<script>
    export default {
        name: 'app-root',
        props: ['title'],
    }
</script>

AppToolbar.vue

<template>
    <v-toolbar :color="$color" app>

        <v-layout row hidden-md-and-up>
            <v-toolbar-side-icon @click="openDrawer" class="white--text"></v-toolbar-side-icon>
        </v-layout>

        <v-toolbar-title class="white--text" v-text="title"></v-toolbar-title>

    </v-toolbar>
</template>

<script>
    export default {
        name: 'app-toolbar',
        props: ['title'],
        methods: {
            openDrawer() {

            }
        }
    }
</script>

AppDrawer.vue

<template>
    <v-navigation-drawer temporary v-model="isDrawerOpen" absolute>
        <v-list class="pa-1">
            <v-list-tile avatar>
                <v-list-tile-avatar>
                    <img src="https://rafaeldeveloper.com.br/img/rafael_developer.jpg">
                </v-list-tile-avatar>
                <v-list-tile-content>
                    <v-list-tile-title>Rafael de Azeredo</v-list-tile-title>
                </v-list-tile-content>
            </v-list-tile>
        </v-list>
    </v-navigation-drawer>
</template>

<script>
    export default {
        name: 'app-drawer',
        data: () => ({
            isDrawerOpen: false
        })
    }
</script>

Upvotes: 5

Views: 16362

Answers (2)

Roy J
Roy J

Reputation: 43899

Components have two kinds of state: internal and external. Internal state is typically controlled by the component's data, while external state usually comes in via props.

When the external state isn't closely related to the parent of the component, you can think of it as application state, or global state. That is usually kept in a "store" that is available to every component. A lot of people use Vuex for that, but I think most of the time you don't really need Vuex, which is largely concerned with synchronizing data affected by lots of possibly-simultaneous input.

In a single-user app, you just need a "godparent": something that, like a parent, you get props from and emit events to, but the relationship isn't as direct.

As it happens, if your application is a collection of components, $root is available to serve as that godparent: you can emit events to it and you can access its data items and computeds.

In your example (which I've stripped down to a minimal snippet below), the state of the drawer is shared across the application. It is not internal to the drawer component. So the drawer component receives its state as a prop; the parent of the drawer — app-root — supplies the prop using :is-open="$root.drawerIsOpen" so the drawer never knows or cares that its state is global; it's just handed down like any prop.

The toolbar component knows that there is a drawer in the app somewhere, not in its own parent, so it emits the open-drawer event to $root:

this.$root.$emit('open-drawer');

And the top-level Vue (which is $root) sets up event handling in its created hook, since there's no way to globally mark up @open-drawer in a template.

This should all feel pretty similar to the "props down, events up" way communication is designed to work in Vue.

Vue.component('app-root', {
  template: '#root-template',
  props: ['title']
});

Vue.component('app-toolbar', {
  template: '#toolbar-template',
  methods: {
    openDrawer() {
      this.$root.$emit('open-drawer');
    }
  }
});

Vue.component('app-drawer', {
  template: '#drawer-template',
  props: ['isOpen']
});

new Vue({
  el: '#app',
  // This is your store!
  data: {
    drawerIsOpen: false
  },
  created() {
    this.$on('open-drawer', () => {
      this.drawerIsOpen = true;
    });
  }
});
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <app-root title="My App!"></app-root>
</div>

<template id="root-template">
  <div>
    <h1>{{title}}</h1>
    <app-toolbar></app-toolbar>
    <app-drawer :is-open="$root.drawerIsOpen"></app-drawer>
  </div>
</template>

<template id="toolbar-template">
  <div>
    <button @click="openDrawer">Open Drawer</button>
  </div>
</template>

<template id="drawer-template">
  <div v-show="isOpen">
    Drawer here
  </div>
</template>

Upvotes: 1

Bhojendra Rauniyar
Bhojendra Rauniyar

Reputation: 85653

First of all, you don't need to use props property in AppRoot.vue file, it will pass down the props when you'll do use :title="title" to the child. props property is used to get the parent component property in the child.

For your query, you can use event bus to communicating between them. Or, you can use vuex for better state management. Here's how we can implement using event bus:

In your main.js file, define a event hub or in any file you want but just make sure event hub is available in your components importing that file.

var eventHub = new Vue();

Now, this will be used in all of your component.

Next, you'll need to emit the event:

AppToolbar.vue

// ...
methods: {
  openDrawer: function () {
    eventHub.$emit('open-drawer', PASS_WHATEVER_YOU_WANT);
  }
}

And then listen to the emitted event on the child:

AppDrawer.vue

// ...
created: function () {
  eventHub.$on('open-drawer', this.openDrawer)
},
methods: {
  openDrawer(value) {
    // ... do whatever you want to do with value
    // this.isDrawerOpen = !this.isDrawerOpen
    this.isDrawerOpen = value;
  }
}

Upvotes: 4

Related Questions