Component Communication Patterns in Vue 2.x
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.