LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1517 min read

@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.ts
import { 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.ts
import { 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

Mutating an @Input() property directly inside the child

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.

Forgetting the type parameter on EventEmitter

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.

Next Lesson →

Content Projection