Emitting Events
Props (Chapter 6) flow data down into a child. To send something back up — a click, a deletion, a new value — a child emits an event that the parent listens for. This is Vue's counterpart to React's "pass a callback down" and Angular's @Output/EventEmitter. Declared with the defineEmits macro.
Emitting an Event with defineEmits
defineEmits(['delete']) declares which events this component can emit and returns an emit function. Calling emit('delete', id) fires the delete event with id as its payload. The parent listens with the @ event syntax from Chapter 3 — @delete="removeTodo" — and removeTodo receives the emitted id as its argument. Structurally identical to Angular's @Output, and the same role as a React callback prop, but modeled as a custom event the child raises (like Angular, unlike React).
@delete — the same syntax as @click — custom events are conventionally named as plain events (delete, save, change), not onDelete. From the parent's perspective <TodoItem @delete="..." /> reads just like listening to a built-in element's event. (This mirrors Angular's @Output naming convention exactly.)
Multiple Events and Validated Emits
A component can declare several events in the array. The object form additionally lets you validate each event's payload with a function (Vue warns if it returns false) — useful documentation of what each event carries, comparable to typing an Angular EventEmitter<T>.
v-model on a Component
Chapter 3 used v-model on a native <input>. You can also put v-model on a custom component, to build something like a reusable input control with two-way binding. Under the hood, v-model on a component is shorthand for a modelValue prop plus an update:modelValue event:
The child takes the current value via the modelValue prop, binds it to its real input with :value, and emits update:modelValue whenever the input changes. The parent then uses plain v-model="name" — Vue wires the prop and event together automatically. This is the exact convention Angular uses for custom two-way binding (value + valueChange), and it's why v-model on native inputs "just works": those are built on the same mechanism.
defineModel(), which collapses the whole modelValue-prop-plus-event pattern above into a single line: const model = defineModel(), then bind v-model="model" on the inner input. It's the recommended modern approach for new code — worth knowing the longer form above too, since it's what defineModel expands to and what you'll see in slightly older code.
v-model component, the child must not reassign modelValue directly — it emits update:modelValue and lets the parent update the source of truth, which flows back down as the new prop. The one-way-data-flow discipline from Chapter 6 holds: data down via props, changes up via events.
| Vue | Angular | React |
|---|---|---|
| defineEmits(['delete']) | @Output() delete | A callback prop (onDelete) |
| emit('delete', id) | this.delete.emit(id) | onDelete(id) |
| @delete="handler" | (delete)="handler($event)" | onDelete={handler} |
| v-model on component | [(value)] (value + valueChange) | value prop + onChange prop |
Coding Challenges
Build a TodoItem component with id/text props and a delete emit. The parent holds an array of todos, renders one TodoItem each, and removes the matching todo when a delete event fires.
📄 View solutionBuild a CustomInput component supporting v-model via a modelValue prop and an update:modelValue emit, then use v-model on it in a parent and display the bound value live.
📄 View solutionRewrite Challenge 2's CustomInput using the newer defineModel() shorthand, confirming the parent's v-model usage is unchanged.
📄 View solutionChapter 7 Quick Reference
- defineEmits([...]) — declares emittable events; returns an
emitfunction - emit('event', payload) — fires an event upward; parent listens with
@event="handler" - Name events plainly (
delete,save), notonX— they read like native events - v-model on a component = a
modelValueprop + anupdate:modelValueevent - defineModel() — the modern one-line shorthand for that whole pattern (Vue 3.4+)
- The child never mutates the prop — it emits, and the parent updates the source of truth
- Next chapter: slots — Vue's content-projection / composition mechanism