Feb 11, 2019

Vue: Parent-Child Communication


Parent <> Child Communication

It is common for any web app to be organized into a tree of nested components. In Vue JS apps, nested components maintain parent <> child relationship. In this article, we will focus on how to establish the communication between them.

In our web apps, we might have components for a header, sidebar, content area and each containing other components for navigation links, etc., It is always good to have the child components independent of parents, so we can reuse them in other scenarios.

Parent to Child Communication:

Parent <> Child Communication

Let us consider a list of blog posts here for our example. There are two components - list of blog posts component (which displays title etc., as Parent) and blog post component (which shows blog title, content etc., as Child).

When the Child component is rendered, it needs to display the data such as Title, blog content etc., The parent component has to pass this data to it. That’s where props come in.

Passing Data to Child Components using Props:

Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance. To pass a title to our blog post component, we can include it in the list of props this component accepts, using a props option:

Here’s a snippet of Child (blog-post) component.

Vue.component('blog-post', {
 props: ['title'],
 template: '<h3>{{ title }}</h3>'
})

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

In an app, however, you’ll likely have an array of posts in data:

new Vue({
 el: '#blog-post-demo',
 data: {
   posts: [
     { id: 1, title: 'My journey with Vue' },
     { id: 2, title: 'Blogging with Vue' },
     { id: 3, title: 'Why Vue is so fun' }
   ]
 }
})

Then, to render a component for each one:

new Vue({
 el: '#blog-posts-events-demo',
 data: {
   posts: [/* ... */],
   postFontSize: 1
 }
})

Above, you’ll see that we can use v-bind to dynamically pass props. This is especially useful when you don’t know the exact content you’re going to render ahead of time, like when fetching posts from an API.

Please refer to the full guide on Props here

Sending Messages to Parent Components with Events

As we develop our <blog-post> component, some features may require communicating back up to the parent. For example, we may decide to include an accessibility feature to enlarge the text of blog posts, while leaving the rest of the page its default size:

In the parent, we can support this feature by adding a postFontSize data property:

new Vue({
 el: '#blog-posts-events-demo',
 data: {
   posts: [/* ... */],
   postFontSize: 1
 }
})

which can be used in the template to control the font size of all blog posts:

<div id="blog-posts-events-demo">
 <div :style="{ fontSize: postFontSize + 'em' }">
   <blog-post
     v-for="post in posts"
     v-bind:key="post.id"
     v-bind:post="post"
   ></blog-post>
 </div>
</div>

Now let’s add a button to enlarge the text right before the content of every post:

Vue.component('blog-post', {
 props: ['post'],
 template: `
   <div class="blog-post">
     <h3>{{ post.title }}</h3>
     <button>
       Enlarge text
     </button>
     <div v-html="post.content"></div>
   </div>
 `
})

The problem is, this button doesn’t do anything:

<button>
 Enlarge text
</button>

When we click on the button, we need to communicate to the parent that it should enlarge the text of all posts. Fortunately, Vue instances provide a custom events system to solve this problem. To emit an event to the parent, we can call the built-in $emit method, passing the name of the event:

<button v-on:click="$emit('enlarge-text')">
 Enlarge text
</button>

Then on our blog post, we can listen for this event with v-on, just as we would with a native DOM event:

<blog-post
 ...
 v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

It’s sometimes useful to emit a specific value with an event. For example, we may want the <blog-post> component to be in charge of how much to enlarge the text by. In those cases, we can use $emit‘s 2nd parameter to provide this value:

<button v-on:click="$emit('enlarge-text', 0.1)">
 Enlarge text
</button>

Then when we listen to the event in the parent, we can access the emitted event’s value with $event:

<blog-post
 ...
 v-on:enlarge-text="postFontSize += $event"
></blog-post>

Or, if the event handler is a method:

<blog-post
 ...
 v-on:enlarge-text="onEnlargeText"
></blog-post>

Then the value will be passed as the first parameter of that method.

methods: {
 onEnlargeText: function (enlargeAmount) {
   this.postFontSize += enlargeAmount
 }
}

Custom Events & v-model

Custom events can also be used to create custom inputs that work with v-model.

Remember that:

<input v-model="searchText">

doing the same thing as follows:

<input
 v-bind:value="searchText"
 v-on:input="searchText = $event.target.value"
>

When used on a component, v-model does this as follows:

<custom-input
 v-bind:value="searchText"
 v-on:input="searchText = $event"
></custom-input>

For this to work, the <input> inside the component must:

  • Bind the value attribute to a value prop
  • On input, emit its own custom input event with the new value.

Here’s that in action:

Vue.component('custom-input', {
 props: ['value'],
 template: `
   <input
     v-bind:value="value"
     v-on:input="$emit('input', $event.target.value)"
   >
 `
})

Now, v-model should work fine with this component:

<custom-input v-model="searchText"></custom-input>

References:

https://vuejs.org/v2/guide/components.html