@Input and @Output
Pass data into a child component with @Input, and communicate back up to the parent with @Output and EventEmitter.
@Input: Parent to Child
The @Input() decorator marks a class property as something a parent component can bind to from the outside, using the standard square-bracket property binding syntax.
// product-card.component.tsimport { Component, Input } from '@angular/core';
@Component({ selector: 'app-product-card', standalone: true, template: ` <div class="card"> <h3>{{ title }}</h3> <p>{{ price }}</p> </div> `,})export class ProductCardComponent { @Input() title = ''; @Input() price = '';}<!-- Parent template --><app-product-card [title]="product.title" [price]="product.price" />Required and Default Inputs
Modern Angular lets you mark an input as required, so the compiler enforces that every usage provides it — catching a missing binding at build time instead of leaving a property silently undefined.
export class ProductCardComponent { @Input({ required: true }) title!: string; @Input() inStock = true; // has a default value}@Output: Child to Parent
@Output() combined with an EventEmitter lets a child component notify its parent that something happened — the parent listens using the standard parenthesis event binding syntax.
// like-button.component.tsimport { Component, Output, EventEmitter } from '@angular/core';
@Component({ selector: 'app-like-button', standalone: true, template: `<button (click)="onLike()">👍 Like</button>`,})export class LikeButtonComponent { @Output() liked = new EventEmitter<void>();
onLike() { this.liked.emit(); }}<!-- Parent template --><app-like-button (liked)="handleLike()" />An EventEmitter can carry data too, exactly like a custom event's payload in other frameworks.
@Output() quantityChanged = new EventEmitter<number>();
increase() { this.quantityChanged.emit(1);}A Custom Two-Way Binding
Naming an @Input() valueX and a matching @Output() valueXChange lets your own component support the [(valueX)] banana-in-a-box syntax, exactly like ngModel does natively.
@Input() value = 0;@Output() valueChange = new EventEmitter<number>();
increment() { this.value++; this.valueChange.emit(this.value);}<app-stepper [(value)]="quantity" />Common Beginner Mistakes
Like React and Vue, Angular treats props flowing down as owned by the parent — hold local state seeded from the input if the child needs to change its own copy.
new EventEmitter() without a generic loses type safety on what the event carries — always specify EventEmitter<T> for the payload type (or EventEmitter<void> for none).
FAQs
Yes — there's no limit; components commonly declare several of each to fully describe their configurable, observable surface.
Yes — modern Angular introduces signal-based input() and output() functions, covered in the signals section later in this course.
Summary
@Input passes data down, and @Output (with EventEmitter) sends events back up — together they form the complete parent-child communication channel. Next, you'll learn content projection, for passing markup (not just data) into a child.