Component Communication

Chapter 5
Component Communication — @Input, @Output, EventEmitter
Passing data into a child and emitting events back up — Angular's equivalent of props and callback props

React passes data down through props and back up by passing functions down as props (Fundamentals Chapters 2 and 4). Angular splits these into two explicit, separately-named mechanisms: @Input for data flowing into a child, and @Output (with an EventEmitter) for events flowing out of a child back to its parent.

@Input — Passing Data Into a Child

// user-card.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-user-card', standalone: true, template: `<h3>{{ name }}</h3><p>Age: {{ age }}</p>`, }) export class UserCardComponent { @Input() name = ''; @Input() age = 0; }
<!-- parent template --> <app-user-card [name]="'Philip'" [age]="35"></app-user-card>

The @Input() decorator marks a property as one the parent is allowed to set — the equivalent of declaring a prop in React. The parent passes it using property binding from Chapter 3: [name]="'Philip'". Note the binding evaluates as an expression, so a literal string needs inner quotes ("'Philip'"), whereas [age]="35" passes a real number — the same string-vs-expression distinction as React's quotes-vs-curly-braces rule for props.

@Output and EventEmitter — Emitting an Event Up

// todo-item.component.ts import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-todo-item', standalone: true, template: ` <li> {{ text }} <button (click)="deleted.emit(id)">Delete</button> </li> `, }) export class TodoItemComponent { @Input() id = 0; @Input() text = ''; @Output() deleted = new EventEmitter<number>(); }
<!-- parent template --> <app-todo-item [id]="todo.id" [text]="todo.text" (deleted)="onDelete($event)" ></app-todo-item>

An @Output() property is an EventEmitter — the child calls .emit(value) on it to send data upward, and the parent listens with event binding (parentheses) exactly like a DOM event: (deleted)="onDelete($event)", where $event holds whatever value was emitted. This is the structural equivalent of React's "pass a function down, child calls it" pattern (Project 1's todo delete), but Angular models it as a custom event the child raises rather than a callback function it receives. The <number> on EventEmitter<number> is a TypeScript generic, declaring the type of value this event carries.

The naming convention mirrors DOM events
Because a parent listens to an @Output with the same (eventName) syntax used for native events like (click), custom outputs are conventionally named as plain past-tense or noun events — deleted, saved, valueChanged — not onDelete. From the parent's side, (deleted)="..." then reads naturally alongside (click)="...", as if the child were a built-in element raising its own events.

A Two-Way Binding Output Convention

If a component has an @Input() called value and an @Output() called valueChange (the input name plus Change), Angular lets a parent use the same [(banana-in-a-box)] two-way syntax from Chapter 3 on it — [(value)]="something". This is exactly how the built-in [(ngModel)] works under the hood, and it's the standard way to build a custom component that supports two-way binding, rather than anything special baked into the framework.

Inputs are not truly read-only the way React props are
A child can technically reassign an @Input() property's value locally, unlike React's strictly read-only props — but doing so is strongly discouraged, since the parent has no idea the value changed and the two will silently disagree. Treat inputs as read-from-parent only: to communicate a change back, emit an @Output event and let the parent update the source of truth, keeping the same one-way-data-flow discipline React enforces structurally.
AngularReact equivalent
@Input() nameA prop
[name]="value"Passing that prop
@Output() saved = new EventEmitter()A callback prop (e.g. onSave)
this.saved.emit(data)Calling that callback: onSave(data)
(saved)="handle($event)"Passing the callback: onSave={handle}

Coding Challenges

Challenge 1

Build a MovieCard component with @Input properties for title and year, rendering them as "Title (Year)". Use it three times in a parent with three different movies.

📄 View solution
Challenge 2

Build a TodoItem component with @Input id/text and an @Output deleted EventEmitter. The parent holds an array of todos, renders one TodoItem each, and removes the matching todo when a delete event is emitted.

📄 View solution
Challenge 3

Build a Counter child component with an @Input count and an @Output countChange EventEmitter, then have the parent use two-way binding [(count)] on it so changing the count inside the child updates the parent's value.

📄 View solution

Chapter 5 Quick Reference

  • @Input() prop — declares a property a parent can set (Angular's "prop")
  • Parent passes it with property binding: [prop]="value"
  • @Output() ev = new EventEmitter<T>() — declares a custom event the child can emit
  • Child raises it with this.ev.emit(data); parent listens with (ev)="handler($event)"
  • Name outputs as events (deleted, saved), not onX — they read like native DOM events
  • An @Input value + @Output valueChange pair enables [(value)] two-way binding
  • Next chapter: services and dependency injection — sharing logic and state beyond parent/child