📡 Component Communication in Vue.js
The Art of Components Talking to Each Other
🎭 The Walkie-Talkie Story
Imagine you have two friends with walkie-talkies. One friend is the Parent and the other is the Child.
- The Parent can talk TO the child anytime (that’s called props)
- The Child can only talk BACK when something happens (that’s called emitting events)
Think of it like a classroom:
- Teacher (Parent) → gives instructions to students
- Student (Child) → raises hand to tell teacher something happened
This is exactly how Vue components communicate!
🎯 What We’ll Learn
- Emitting Events - Child says “Hey Parent, something happened!”
- emit Function - The magic tool to send messages up
- Event Validation - Making sure messages are correct
- v-model on Components - Two-way radio communication
- Multiple v-model Bindings - Many radios at once
- v-model Arguments - Naming your radio channels
- Custom Modifier Creation - Special message filters
1️⃣ Emitting Events
What is it?
When a child component wants to tell its parent “Hey! Something happened!”, it emits an event.
Simple Example
Think of a doorbell. When you press it (child), it rings inside the house (parent).
<!-- ChildButton.vue -->
<template>
<button @click="ringDoorbell">
Ring Bell! 🔔
</button>
</template>
<script setup>
const emit = defineEmits(['doorbell-rang'])
function ringDoorbell() {
emit('doorbell-rang')
}
</script>
<!-- Parent.vue -->
<template>
<ChildButton @doorbell-rang="answer" />
</template>
<script setup>
function answer() {
console.log('Someone is at the door!')
}
</script>
How it works:
graph TD A["👶 Child: Button Clicked"] --> B["📤 emit doorbell-rang"] B --> C["👨 Parent: Listens with @doorbell-rang"] C --> D["✅ Parent runs answer function"]
2️⃣ The emit Function
What is it?
emit is your walkie-talkie. It sends messages from child to parent.
Basic Usage
<script setup>
// Step 1: Define what events you can send
const emit = defineEmits(['message-sent'])
// Step 2: Use emit to send
function sendMessage() {
emit('message-sent', 'Hello Parent!')
}
</script>
Sending Data with Events
You can attach data to your message!
<!-- ScoreCounter.vue -->
<script setup>
const emit = defineEmits(['score-changed'])
function addPoints(points) {
emit('score-changed', points)
}
</script>
<template>
<button @click="addPoints(10)">
+10 Points
</button>
</template>
<!-- Parent.vue -->
<template>
<ScoreCounter @score-changed="updateScore" />
<p>Total: {{ total }}</p>
</template>
<script setup>
import { ref } from 'vue'
const total = ref(0)
function updateScore(points) {
total.value += points
}
</script>
Multiple Arguments
Send many pieces of info at once!
emit('user-registered', name, email, age)
3️⃣ Event Validation
What is it?
Making sure the messages you send are correct. Like a spell-checker for your walkie-talkie!
Why Validate?
- Catch mistakes early
- Know exactly what data to expect
- Better debugging
How to Validate
<script setup>
const emit = defineEmits({
// Simple: just name the event
'click': null,
// With validation: return true/false
'submit': (email) => {
if (!email) {
console.warn('Email is required!')
return false
}
if (!email.includes('@')) {
console.warn('Invalid email!')
return false
}
return true
}
})
</script>
Real Example: Age Validator
<script setup>
const emit = defineEmits({
'age-updated': (age) => {
// Must be a number
if (typeof age !== 'number') {
console.warn('Age must be a number!')
return false
}
// Must be positive
if (age < 0 || age > 150) {
console.warn('Age must be 0-150!')
return false
}
return true
}
})
function updateAge(newAge) {
emit('age-updated', newAge)
}
</script>
Validation Flow
graph TD A["emit called"] --> B{Validation Function} B -->|returns true| C["✅ Event Sent"] B -->|returns false| D["⚠️ Warning in Console"] D --> E["Event Still Sent"]
Note: Validation warnings help during development but don’t stop the event!
4️⃣ v-model on Components
What is it?
A two-way street for data. Parent and child stay in sync automatically!
The Problem Without v-model
Normally, you need:
- A prop going DOWN ⬇️
- An event going UP ⬆️
That’s two things to manage!
The Magic of v-model
v-model does BOTH in one line!
How Components Use v-model
Step 1: Child receives modelValue prop
Step 2: Child emits update:modelValue event
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="emit('update:modelValue',
$event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<!-- Parent.vue -->
<template>
<CustomInput v-model="username" />
<p>Hello, {{ username }}!</p>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>
Visual Flow
graph TD A["Parent: username"] -->|modelValue prop| B["Child Input"] B -->|update:modelValue| A A --> C["Both stay in sync!"]
Cleaner with defineModel 🆕
Vue 3.4+ makes it even simpler:
<!-- CustomInput.vue -->
<template>
<input v-model="model" />
</template>
<script setup>
const model = defineModel()
</script>
That’s it! No props or emit to write!
5️⃣ Multiple v-model Bindings
What is it?
Sometimes you need to sync MANY values. Like having multiple walkie-talkie channels!
Example: User Form
<!-- UserForm.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')
</script>
<template>
<input v-model="firstName"
placeholder="First name" />
<input v-model="lastName"
placeholder="Last name" />
<input v-model="email"
placeholder="Email" />
</template>
<!-- Parent.vue -->
<template>
<UserForm
v-model:firstName="first"
v-model:lastName="last"
v-model:email="userEmail"
/>
<p>{{ first }} {{ last }}</p>
<p>📧 {{ userEmail }}</p>
</template>
<script setup>
import { ref } from 'vue'
const first = ref('')
const last = ref('')
const userEmail = ref('')
</script>
The Old Way (Still Works)
<script setup>
defineProps({
firstName: String,
lastName: String
})
const emit = defineEmits([
'update:firstName',
'update:lastName'
])
</script>
<template>
<input
:value="firstName"
@input="emit('update:firstName',
$event.target.value)"
/>
</template>
6️⃣ v-model Arguments
What is it?
Giving names to your v-model channels. Default is modelValue, but you can use any name!
Default vs Named
<!-- Default (no argument) -->
<CustomInput v-model="text" />
<!-- equals: v-model:modelValue="text" -->
<!-- Named argument -->
<CustomInput v-model:title="pageTitle" />
Child Component with Named v-model
<!-- BookEditor.vue -->
<script setup>
const title = defineModel('title')
const author = defineModel('author')
</script>
<template>
<div>
<label>Title:</label>
<input v-model="title" />
<label>Author:</label>
<input v-model="author" />
</div>
</template>
<!-- Parent.vue -->
<template>
<BookEditor
v-model:title="book.title"
v-model:author="book.author"
/>
</template>
Why Use Arguments?
- Clarity: Know exactly what each v-model controls
- Multiple bindings: Each has its own name
- Self-documenting: Code explains itself
7️⃣ Custom Modifier Creation
What is it?
Modifiers are like filters for your data. You already know some:
.trim- removes extra spaces.number- converts to number.lazy- updates on blur, not input
Now you can make YOUR OWN!
How Modifiers Work
When you write v-model.capitalize="text":
- Vue passes
modelModifiers: { capitalize: true } - Your component checks for this and transforms data
Example: Capitalize Modifier
<!-- CustomInput.vue -->
<script setup>
const model = defineModel({
set(value) {
if (props.modelModifiers?.capitalize) {
return value.charAt(0).toUpperCase()
+ value.slice(1)
}
return value
}
})
const props = defineProps({
modelModifiers: { default: () => ({}) }
})
</script>
<template>
<input v-model="model" />
</template>
<!-- Parent.vue -->
<template>
<CustomInput v-model.capitalize="name" />
<!-- Types "john" → becomes "John" -->
</template>
Multiple Modifiers
<CustomInput v-model.trim.capitalize="name" />
<script setup>
const model = defineModel({
set(value) {
let result = value
if (props.modelModifiers?.trim) {
result = result.trim()
}
if (props.modelModifiers?.capitalize) {
result = result.charAt(0).toUpperCase()
+ result.slice(1)
}
return result
}
})
</script>
Modifiers with Arguments
For named v-models like v-model:title.capitalize:
<script setup>
const title = defineModel('title', {
set(value) {
if (props.titleModifiers?.capitalize) {
return value.toUpperCase()
}
return value
}
})
const props = defineProps({
titleModifiers: { default: () => ({}) }
})
</script>
🎯 Quick Reference
| Concept | Purpose | Key Syntax |
|---|---|---|
| Emitting Events | Child → Parent messages | emit('event-name') |
| emit Function | Send events up | defineEmits([...]) |
| Validation | Check event data | defineEmits({ event: validator }) |
| v-model | Two-way binding | defineModel() |
| Multiple v-model | Many bindings | v-model:name="val" |
| Arguments | Named channels | defineModel('name') |
| Modifiers | Transform data | modelModifiers prop |
🏆 You Did It!
You now understand how Vue components talk to each other:
- ✅ Events go UP (child to parent)
- ✅ Props go DOWN (parent to child)
- ✅ v-model makes two-way binding easy
- ✅ Modifiers let you transform data
Remember: Components are like a family. Parents give instructions (props), children report back (emit), and sometimes they need two-way conversations (v-model)!
Happy coding! 🚀
