Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Component Communication Patterns in Vue 2.x

Tech 2

Parent-Child Communication

1. Parent to Child via Props

Props are the primary mechanism for passing data from a parent component to a child component.

<!-- Parent Component Template -->
<div id="app">
    <product-catalog :products="productList" :count="productList.length"></product-catalog>
    <hr>
    <product-catalog></product-catalog>
</div>

<!-- Child Component Template -->
<template id="catalogTemplate">
    <ul>
        <li v-for="(product, idx) in products" :key="idx">
            <img :src="product.imageUrl" alt="" style="display:block; width: 150px; height: 150px; border: 1px solid #f66">
            {{ product.name }}
        </li>
    </ul>
</template>

<script>
const ProductCatalog = {
    props: {
        products: {
            type: Array,
            default: function() {
                return [
                    { imageUrl: 'placeholder.jpg', name: 'No data provided.' }
                ]
            }
        }
    },
    template: '#catalogTemplate'
}

new Vue({
    el: '#app',
    data: {
        productList: [
            { imageUrl: 'https://example.com/jacket.jpg', name: 'Men\'s Jacket - Black, Size XL' },
            { imageUrl: 'https://example.com/laptop.jpg', name: 'MacBook Pro 15-inch, 2018 Model' }
        ]
    },
    components: {
        'product-catalog': ProductCatalog
    }
})
</script>

2. Child to Parent via Custom Events ($emit)

A child compnoent can communicate with its parent by emitting custom events.

<!-- Parent Component Template -->
<div id="app">
    <h1>Parent Component</h1>
    <p>Message from child: {{ childMessage }}</p>
    <child-component @message-from-child="handleChildMessage"></child-component>
</div>

<!-- Child Component Template -->
<template id="childTemplate">
    <div>
        <button @click="sendMessageToParent">Send Message to Parent</button>
    </div>
</template>

<script>
const ChildComponent = {
    template: '#childTemplate',
    methods: {
        sendMessageToParent() {
            this.$emit('message-from-child', 'Hello from the child component!')
        }
    }
}

new Vue({
    el: '#app',
    data: {
        childMessage: ''
    },
    components: {
        'child-component': ChildComponent
    },
    methods: {
        handleChildMessage(msg) {
            this.childMessage = msg
        }
    }
})
</script>

3. Accessing Parent Directly via $parent

A child component can directly access its parent's instance, including data and methods.

<!-- Parent Component -->
<template>
  <div>
    <h1>Parent Component</h1>
    <child-comp></child-comp>
  </div>
</template>
<script>
import ChildComp from './ChildComp.vue'
export default {
  name: 'ParentComp',
  components: { ChildComp },
  data() {
    return { parentData: 'Information from parent' }
  },
  methods: {
    parentMethod() { console.log('Parent method executed.') }
  }
}
</script>

<!-- Child Component -->
<template>
  <div>
    <h2>Child Component</h2>
    <button @click="accessParent">Access Parent</button>
  </div>
</template>
<script>
export default {
  name: 'ChildComp',
  methods: {
    accessParent() {
      console.log(this.$parent) // Parent instance
      console.log(this.$parent.parentData) // Access parent data
      this.$parent.parentMethod() // Call parent method
    }
  }
}
</script>

4. Accessing Children Directly via $children

A parent component can directly access its child component instances.

<!-- Parent Component -->
<template>
  <div>
    <h1>Parent Component</h1>
    <child-comp></child-comp>
  </div>
</template>
<script>
import ChildComp from './ChildComp.vue'
export default {
  name: 'ParentComp',
  components: { ChildComp },
  mounted() {
    console.log(this.$children) // Array of child instances
    console.log(this.$children[0].childData) // Access child data
    this.$children[0].childMethod() // Call child method
  }
}
</script>

<!-- Child Component -->
<template>
  <div>
    <h2>Child Component</h2>
  </div>
</template>
<script>
export default {
  name: 'ChildComp',
  data() { return { childData: 'Data from child' } },
  methods: { childMethod() { console.log('Child method.') } }
}
</script>

5. Using ref for Direct Access

ref can be used to obtain a direct reference to a child component instance or a DOM element.

<!-- Parent Component -->
<template>
    <div>
        <h1 ref="pageTitle">Main Title</h1>
        <p ref="description">This is a description paragraph.</p>
        <button @click="logRefs">Log Refs</button>
        <child-ref ref="childRefInstance"/>
        <button @click="modifyChildData">Modify Child Data</button>
    </div>
</template>
<script>
import ChildRef from './ChildRef.vue'
export default {
    components: { ChildRef },
    methods: {
        logRefs() {
            console.log(this.$refs.pageTitle) // DOM element
            console.log(this.$refs.description) // DOM element
            console.log(this.$refs.childRefInstance) // Child component instance
        },
        modifyChildData() {
            this.$refs.childRefInstance.message = 'Modified by parent'
            console.log(this.$refs.childRefInstance.message)
        }
    }
}
</script>

<!-- Child Component -->
<template>
  <div>
    <button @click="getParentInfo">Get Parent Info via $parent</button>
  </div>
</template>
<script>
export default {
  data() { return { message: 'Original child message' } },
  methods: {
    getParentInfo() {
      // Accessing parent via $parent (alternative to refs)
      console.log(this.$parent.someData)
    }
  }
}
</script>

6. Provide and Inject for Deep Nesting

provide and inject enable ancestor components to serve as dependency providers for all their descendants.

<!-- Ancestor (Provider) Component -->
<template>
    <div id="app">
        <!-- Child components go here -->
    </div>
</template>
<script>
export default {
    data() {
        return {
            items: [ { id: 1, text: 'Product One' } ]
        }
    },
    provide() {
        return {
            providedItems: this.items
        }
    }
}
</script>

<!-- Descendant (Injector) Component -->
<template>
    <div>
        <ul>
        <li v-for="item in providedItems" :key="item.id">
            {{ item.text }}
        </li>
        </ul>
    </div>
</template>
<script>
export default {
    inject: ['providedItems']
}
</script>

Sibling Component Communication

Using an Event Bus

A Vue instance can act as a central event bus for communication between unrelated components.

<div id="app">
    <content-panel></content-panel>
    <navigation-bar></navigation-bar>
</div>

<template id="contentPanel">
    <div>Current Section: {{ activeSection }}</div>
</template>

<template id="navBar">
    <ul>
        <li @click="switchSection('Home')">Home</li>
        <li @click="switchSection('Categories')">Categories</li>
        <li @click="switchSection('Cart')">Cart</li>
        <li @click="switchSection('Account')">Account</li>
    </ul>
</template>

<script>
const eventHub = new Vue()

const ContentPanel = {
    template: '#contentPanel',
    data() { return { activeSection: '' } },
    mounted() {
        eventHub.$on('section-changed', (section) => {
            this.activeSection = section
        })
    }
}

const NavigationBar = {
    template: '#navBar',
    methods: {
        switchSection(sectionName) {
            eventHub.$emit('section-changed', sectionName)
        }
    },
    mounted() {
        eventHub.$emit('section-changed', 'Home')
    }
}

new Vue({
    el: '#app',
    components: {
        'content-panel': ContentPanel,
        'navigation-bar': NavigationBar
    }
})
</script>

Cross-Level Component Communication

1. Passing Attributes with $attrs

$attrs contains attribute bindings (except class and style) that are not declared as props.

<!-- Root Component -->
<template>
    <div>
        <middle-component :user-name="name" :user-age="age"/>
    </div>
</template>
<script>
export default {
    data() { return { name: 'Alice', age: '25' } }
}
</script>

<!-- Middle Component -->
<template>
    <div>
        <leaf-component v-bind="$attrs"/>
    </div>
</template>
<script>
export default {
    props: ['userName'], // userName is consumed as a prop
    mounted() {
        // $attrs now only contains userAge
        console.log(this.$attrs) // Output: { userAge: '25' }
    }
}
</script>

<!-- Leaf Component -->
<template>
    <div>Leaf Component</div>
</template>
<script>
export default {
    mounted() {
        // Receives the passed-down attribute
        console.log(this.$attrs) // Output: { userAge: '25' }
    }
}
</script>

2. Passing Listeners with $listeners

$listeners contains all event listeners (excluding those with the .native modifier) on the component.

<!-- Root Component -->
<template>
    <div>
        <middle-component @custom-event="handleCustomEvent" @click.native="handleNativeClick"/>
    </div>
</template>
<script>
export default {
    methods: {
        handleCustomEvent() { console.log('Custom event handled in root.') },
        handleNativeClick() { console.log('Native click handled in root.') }
    }
}
</script>

<!-- Middle Component -->
<template>
    <div>
        <leaf-component v-on="$listeners"/>
    </div>
</template>
<script>
export default {
    mounted() {
        // $listeners contains only custom-event (click.native is excluded)
        console.log(this.$listeners) // Output: { 'custom-event': fn }
        this.$emit('custom-event') // Can trigger the root handler
    }
}
</script>

<!-- Leaf Component -->
<template>
    <div>Leaf Component</div>
</template>
<script>
export default {
    mounted() {
        console.log(this.$listeners) // Output: { 'custom-event': fn }
        this.$emit('custom-event') // Can also trigger the root handler
    }
}
</script>

Global State Management with Vuex

For complex applications, Vuex provieds a centralized store for state management across all components, offering a predictable state mutation pattern.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.