Skip to main content

@input and @output decorators in Angular

@Input and @Output are Angular decorators that wire parent-child component communication: @Input passes data from parent to child, @Output emits events from child to parent.

Theory

TL;DR

  • @Input = parent hands data down; the child reads it but cannot push changes back to the parent's copy
  • @Output = child fires an event upward using EventEmitter; the parent decides what to do with it
  • Analogy: parent passes a note to a kid (@Input); the kid shouts "Done!" back (@Output)
  • Use both for reusable, presentational components; switch to a service when siblings share state or state is global

Quick example

typescript
// child.component.ts import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-child', template: `<button (click)="notify()">Finish</button>` }) export class ChildComponent { @Input() message = 'Hello'; // receives from parent @Output() done = new EventEmitter<string>(); // sends to parent notify() { this.done.emit('Task complete!'); } }
html
<!-- parent.component.html --> <app-child [message]="parentMsg" (done)="onDone($event)"></app-child>

[message] is property binding - data flows in. (done) is event binding - the parent method runs when the child calls .emit().

Key difference

@Input creates a one-way data binding: when the parent value changes, Angular updates the child during the next change detection cycle. @Output works in the opposite direction - the child holds an EventEmitter and calls .emit() to trigger a parent method. The child has no idea what the parent will do with that event, which keeps the two components decoupled.

When to use

  • Pass user profile data to a display card component → @Input
  • A delete button in a list item needs to notify the parent → @Output
  • A reusable field that shows a value and reports changes → both
  • Two sibling components share state, or state is needed across multiple features → skip both, use a service with BehaviorSubject or NgRx

How Angular handles it internally

Angular's compiler processes @Input and @Output during template compilation, turning them into property bindings and event listeners. When an @Input value changes, Angular calls ngOnChanges on the child. @Output wraps EventEmitter in zone.js-patched events, so each .emit() schedules a change detection run rather than triggering it immediately. This avoids infinite loops when an output causes a parent state change.

Common mistakes

Forgetting parentheses on the output binding:

html
<!-- Wrong: Angular treats this as a property binding, not an event --> <app-child done="handle()"></app-child> <!-- Correct --> <app-child (done)="handle($event)"></app-child>

Mutating the @Input array or object directly in the child:

typescript
// Wrong: parent never knows the array changed @Input() items: string[] = []; ngOnInit() { this.items.push('new'); } // Correct: emit the updated collection back @Output() itemsChange = new EventEmitter<string[]>(); add(item: string) { this.itemsChange.emit([...this.items, item]); }

One-way binding means the parent's reference is not updated. Developers switching from React sometimes hit this - in React, props are immutable by convention, but Angular does not enforce it at the object level.

Using @Output without EventEmitter:

typescript
// Wrong: TypeScript compiles this, but runtime throws @Output() done: string; // Correct @Output() done = new EventEmitter<string>();

Skipping transform for boolean attributes:

html
<!-- Wrong: passes the string "true", not a boolean --> <app-button [disabled]="'true'"></app-button>
typescript
// Correct: booleanAttribute converts the string to a real boolean @Input({ transform: booleanAttribute }) disabled = false;

Real-world usage

  • Angular Material: mat-slider uses @Input() value and @Output() valueChange for two-way binding
  • PrimeNG: p-table takes @Input() options and emits @Output() onRowSelect
  • NG Bootstrap: ngb-datepicker accepts a model via @Input and fires @Output() navigate on month change
  • Angular's built-in required option (since Angular 14.3+): @Input({ required: true }) id!: string throws NG0303 in dev mode if the parent forgets to bind it

Follow-up questions

Q: What is the difference between @Input and property binding?
A: @Input marks a class property as bindable from outside the component. Property binding [prop]="value" in the parent template is how you actually supply the value. One is the declaration, the other is the usage.

Q: How does ChangeDetectionStrategy.OnPush interact with @Input?
A: With OnPush, Angular only re-checks a component when its @Input references change (not deep mutations), or when an event fires. If you mutate an @Input array in place without creating a new reference, the component will not re-render.

Q: What does @Input({ required: true }) do?
A: Available since Angular 14.3+. The compiler throws NG0303 at build time if the parent does not bind that input. Useful for properties that make no sense without a value, like a row id in a data table.

Q: Why use EventEmitter instead of a plain Subject for @Output?
A: Angular template event binding is designed to work with EventEmitter. A Subject will function at runtime, but EventEmitter communicates intent clearly and keeps the API consistent with Angular conventions.

Q: When does passing data through @Input/@Output become a problem, and what do you do instead?
A: Once data has to pass through more than two levels of nesting, or two sibling components need the same state, it gets unwieldy fast. The standard move is a shared service with a BehaviorSubject. Add ChangeDetectionStrategy.OnPush across those components to cut unnecessary re-renders. For large-scale cross-feature state, NgRx or Angular Signals store are the right tools.

Examples

Basic: counter with two-way sync

typescript
// counter.component.ts @Component({ selector: 'app-counter', template: ` <p>Count: {{ count }}</p> <button (click)="increment()">+</button> ` }) export class CounterComponent { @Input() count = 0; @Output() countChange = new EventEmitter<number>(); increment() { this.countChange.emit(this.count + 1); // never mutates count directly } }
html
<!-- parent.component.html --> <app-counter [count]="counter" (countChange)="counter = $event"></app-counter>

The child never modifies its own count. It emits the new value and the parent updates the source of truth. Name an input/output pair as value and valueChange and Angular's [(value)] two-way binding syntax works automatically.

Intermediate: user list with multiple outputs

typescript
// user-item.component.ts @Component({ selector: 'app-user-item', template: ` <div> {{ user.name }} ({{ user.active ? 'Active' : 'Inactive' }}) <button (click)="toggle()">Toggle</button> <button (click)="remove()">Delete</button> </div> ` }) export class UserItemComponent { @Input() user!: { name: string; active: boolean }; @Output() userToggled = new EventEmitter<{ name: string; active: boolean }>(); @Output() userDeleted = new EventEmitter<string>(); toggle() { this.userToggled.emit({ ...this.user, active: !this.user.active }); } remove() { this.userDeleted.emit(this.user.name); } }
html
<!-- parent.component.html --> <app-user-item *ngFor="let u of users" [user]="u" (userToggled)="updateUser($event)" (userDeleted)="deleteUser($event)" ></app-user-item>

One component, two outputs. The parent owns the users array and is responsible for updating it. The child only knows its own user object and which button was clicked. Business logic stays out of the display component.

Advanced: required input with alias and transform

typescript
// data-row.component.ts @Component({ selector: 'app-data-row', template: `<p>ID: {{ id }} | Disabled: {{ disabled }}</p>` }) export class DataRowComponent { @Input({ required: true, alias: 'rowId' }) id!: string; @Input({ transform: booleanAttribute }) disabled = false; }
html
<!-- Works fine --> <app-data-row [rowId]="'abc-123'" disabled></app-data-row> <!-- Throws NG0303 in dev: Missing required input 'rowId' --> <app-data-row></app-data-row>

required: true makes the compiler catch a missing binding before runtime. alias lets the template use rowId while the class property is named id. booleanAttribute handles the string-to-boolean conversion: the attribute disabled with no value becomes true, not "true".

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?