Component Communication
Components rarely work alone. A list tells a row what to show; the row tells the list it was deleted; two distant widgets share a cart. Angular has a clear tool for each direction.
On this page · 6 min · Advanced
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Pass data down with input()
- Send events up with output()
- Call a child directly with viewChild
- Share data between siblings with a service and project content with ng-content
Explanation#
Parent → child: input(). The parent binds [product]="p"; the child reads product().
Child → parent: output(). The child declares deleted = output<number>() and calls this.deleted.emit(id); the parent listens with (deleted)="remove($event)".
Both ways: model() for an editable value, bound with [(value)].
Parent calling a child method: rating = viewChild(RatingComponent) and then this.rating()?.reset(). Use sparingly — inputs and outputs keep components decoupled.
Siblings or distant components: share a service with signals (a cart, the current user).
Content projection: a Card component with <ng-content /> displays whatever the parent puts between its tags; named slots use <ng-content select="[card-footer]" />.
Syntax#
// child
product = input.required<Product>();
deleted = output<number>();
remove() { this.deleted.emit(this.product().id); }
// parent template
<app-product-row [product]="p" (deleted)="remove($event)" />
Code example: A list, rows, a card with projected content, and viewChild#
import { Component, input, output, signal, viewChild } from '@angular/core';
type Todo = { id: number; title: string; done: boolean };
@Component({
selector: 'app-card',
template: `
<section class="card">
<header><ng-content select="[card-title]" /></header>
<ng-content />
<footer><ng-content select="[card-footer]" /></footer>
</section>
`,
styles: `.card { border: 1px solid #ddd; border-radius: 8px; padding: 8px; }`,
})
export class Card {}
@Component({
selector: 'app-todo-row',
template: `
<label>
<input type="checkbox" [checked]="todo().done" (change)="toggled.emit(todo().id)" />
{{ todo().title }}
</label>
<button (click)="deleted.emit(todo().id)">✕</button>
`,
})
export class TodoRow {
todo = input.required<Todo>();
toggled = output<number>();
deleted = output<number>();
}
@Component({
selector: 'app-new-todo',
template: `<input #box (keyup.enter)="add(box.value); box.value = ''" placeholder="New todo" />`,
})
export class NewTodo {
created = output<string>();
count = 0;
add(title: string) {
if (title.trim()) { this.created.emit(title.trim()); this.count++; }
}
reset() { this.count = 0; }
}
@Component({
selector: 'app-todo-board',
imports: [Card, TodoRow, NewTodo],
template: `
<app-card>
<h3 card-title>My todos ({{ remaining() }} left)</h3>
@for (t of todos(); track t.id) {
<app-todo-row [todo]="t" (toggled)="toggle($event)" (deleted)="remove($event)" />
}
<app-new-todo (created)="add($event)" />
<small card-footer>Added this session: {{ newTodo()?.count ?? 0 }} <button (click)="resetCounter()">reset</button></small>
</app-card>
`,
})
export class TodoBoard {
todos = signal<Todo[]>([{ id: 1, title: 'Learn inputs', done: true }, { id: 2, title: 'Learn outputs', done: false }]);
newTodo = viewChild(NewTodo);
private nextId = 3;
remaining() { return this.todos().filter(t => !t.done).length; }
add(title: string) { this.todos.update(l => [...l, { id: this.nextId++, title, done: false }]); }
toggle(id: number) { this.todos.update(l => l.map(t => (t.id === id ? { ...t, done: !t.done } : t))); }
remove(id: number) { this.todos.update(l => l.filter(t => t.id !== id)); }
resetCounter() { this.newTodo()?.reset(); }
}
A bordered card titled “My todos (1 left)” lists two todos. Ticking a box or clicking ✕ emits an event the board handles, and the title updates. Typing a todo and pressing Enter adds it and increases “Added this session”. The reset button calls NewTodo.reset() through viewChild.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- TodoRow receives a todo via input and reports toggles and deletions via outputs — it never changes the list itself.
- TodoBoard owns the list and reacts to $event (the emitted id).
- NewTodo emits the new title; the board decides how to store it.
- viewChild(NewTodo) gives the board direct access to call reset().
- Card projects content: the title, body and footer come from the parent.
Real-world use case#
A data table component takes rows and columns as inputs, emits (sortChange) and (rowSelected) outputs, and projects custom cell templates — reused across dozens of screens.
Common mistakes#
A row that edits the shared array directly makes data flow hard to follow. Emit an event; let the owner change the data.
Calling many child methods creates tight coupling. Prefer inputs and outputs.
Passing the same data through five levels is painful. Use a shared service.
Best practices#
- Data down with inputs, events up with outputs
- One owner per piece of state
- Services for siblings and distant components
- ng-content for layout components
Remember this#
input() down · output() up · model() both ways.
Siblings → shared service.
<ng-content /> = parent decides the content.
Practice exercise#
Your task: Create a Rating component with a stars input and a rated output that emits 1–5 when a star is clicked, and a parent that shows "You rated 4 stars".
Need a hint?
Show solution
import { Component, input, output, signal } from '@angular/core';
@Component({
selector: 'app-rating',
template: `@for (n of [1, 2, 3, 4, 5]; track n) {
<button (click)="rated.emit(n)" [attr.aria-label]="n + ' stars'">{{ n <= stars() ? '★' : '☆' }}</button>
}`,
})
export class Rating {
stars = input(0);
rated = output<number>();
}
@Component({
selector: 'app-review',
imports: [Rating],
template: `<app-rating [stars]="stars()" (rated)="stars.set($event)" /> <p>You rated {{ stars() }} stars</p>`,
})
export class Review {
stars = signal(0);
}
Mini quiz#
How does a child notify its parent?
Want more? Practise all Angular MCQs · Angular interview questions
My notes#
Saved in this browser.