@Input and @Output in Angular: Data Flow and Event Communication Explained
@Input enables parent components to pass data down to child components, while @Output allows child components to emit events and data back up to parent components through EventEmitter instances.
In Angular applications, component communication relies on two fundamental decorators that establish clear contracts for data flow. Understanding how @Input and @Output work in Angular is essential for building maintainable component hierarchies. This guide examines the actual implementation in the angular/angular repository to explain the architectural differences, runtime behavior, and modern signal-based alternatives.
What Are @Input and @Output in Angular?
Angular components communicate across the component tree using a unidirectional data flow pattern. The framework provides two distinct decorators to formalize this communication:
-
@Input: Declares a property that receives data from a parent component's template binding. The Angular compiler generates property-binding instructions that copy the bound expression into the child instance during each change-detection cycle.
-
@Output: Declares a property that holds an
EventEmitter(or anyOutputRefimplementation). The runtime creates listener instructions that subscribe to the emitter and forward emitted values as events to the parent's template binding.
How @Input Works: Parent to Child Data Flow
When you decorate a property with @Input(), the Angular compiler processes this decorator in packages/compiler-cli/src/ngtsc/annotations/directive/src/input_function.ts. The parser builds an InputMapping metadata object that stores:
- The class property name
- The public binding name (alias)
- Whether the input is a signal (
isSignal: true)
At runtime, the Ivy compiler generates property instructions that write the bound value to the instance field. For standard decorator-based inputs, this occurs during change detection. For signal-based inputs, the signal updates automatically when the parent binding changes.
Classic @Input Example
// child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `<h2>{{ title }}</h2><p>{{ bio }}</p>`
})
export class UserCardComponent {
@Input() title: string = '';
@Input() bio: string = '';
}
<!-- parent.component.html -->
<app-user-card [title]="user.name" [bio]="user.description"></app-user-card>
How @Output Works: Child to Parent Event Communication
The @Output decorator, parsed in packages/compiler-cli/src/ngtsc/annotations/directive/src/output_function.ts, creates InputOrOutput metadata that records the property name and ensures the field is accessible (public or protected). Unlike inputs, outputs are not signal-based; they rely on EventEmitter or custom OutputRef implementations.
At runtime, the Angular runtime generates listener instructions that subscribe to the emitter. When the child calls emit(), the runtime triggers the parent's handler function.
Classic @Output Example
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-save-button',
template: `<button (click)="onClick()">Save</button>`
})
export class SaveButtonComponent {
@Output() save = new EventEmitter<void>();
onClick() {
this.save.emit();
}
}
<!-- parent.component.html -->
<app-save-button (save)="handleSave()"></app-save-button>
Signal-Based Inputs in Angular 16+
Since Angular 16, the framework provides signal-based inputs through the input() function implemented in packages/core/src/authoring/input/input.ts. These inputs create InputSignal instances that provide fine-grained reactivity without requiring change detection cycles for updates.
Signal inputs work alongside the @Input decorator or replace it entirely when using the new authoring format. They support required inputs via input.required<T>() and automatically update when parent bindings change.
Signal Input with @Output Example
// child-signal.component.ts
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-greeter',
template: `
<h2>Hello, {{ name() }}!</h2>
<button (click)="sayHi.emit()">Greet</button>
`,
})
export class GreeterComponent {
// Signal input – automatically updates when parent binding changes
name = input<string>('Guest');
// Output – still an EventEmitter under the hood
sayHi = output<void>();
}
<!-- parent.component.html -->
<app-greeter [name]="'Alice'" (sayHi)="onGreet()"></app-greeter>
Two-Way Binding: Combining @Input and @Output
Angular implements two-way binding syntax [(property)] as a combination of an @Input property and an @Output event with the same name plus the Change suffix. This pattern allows components to both receive and emit values.
Two-Way Binding Implementation
// slider.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-slider',
template: `<input type="range" [value]="value" (input)="onInput($event)" />`,
})
export class SliderComponent {
@Input() value = 0;
@Output() valueChange = new EventEmitter<number>();
onInput(event: Event) {
const newVal = +(event.target as HTMLInputElement).value;
this.value = newVal;
this.valueChange.emit(newVal);
}
}
<!-- parent.html -->
<app-slider [(value)]="volume"></app-slider>
When to Use @Input vs @Output
Understanding the direction of data flow determines which decorator to use:
| Scenario | Use @Input | Use @Output |
|---|---|---|
| Parent provides configuration data (strings, objects, booleans) | ✅ | ❌ |
| Child notifies parent of actions (clicks, submissions, async completion) | ❌ | ✅ |
| Two-way synchronization | Both: @Input() value + @Output() valueChange |
Both: @Input() value + @Output() valueChange |
| Signal-based reactive data | input<T>() or input.required<T>() |
Still use @Output or output() for events |
| Sibling component communication | ❌ (use shared service) | ❌ (use shared service) |
Summary
-
@Input establishes a parent-to-child data contract, parsed by the compiler in
input_function.tsand implemented as property bindings that update during change detection. -
@Output establishes a child-to-parent event contract, parsed in
output_function.tsand implemented usingEventEmitterinstances that trigger parent handlers via listener instructions. -
Signal inputs (
input()andinput.required()) provide fine-grained reactivity without full change detection cycles, implemented inpackages/core/src/authoring/input/input.ts. -
Two-way binding combines an
@Inputproperty with an@Outputevent using theChangesuffix convention.
Frequently Asked Questions
Can @Input and @Output be used together for two-way binding?
Yes, Angular implements the "banana in a box" syntax [(property)] by pairing an @Input property with an @Output event that uses the same name plus the Change suffix. For example, @Input() value combined with @Output() valueChange allows parents to use [(value)]="data" for synchronized updates.
What is the difference between EventEmitter and the output() function?
The output() function, available in newer Angular versions, provides a more type-safe and ergonomic API for creating outputs, but it still creates an EventEmitter under the hood according to the source code in output_function.ts. Both approaches generate InputOrOutput metadata and use the same runtime listener instructions, though output() simplifies the declaration syntax.
How do signal-based inputs differ from decorator-based @Input?
Signal-based inputs created with input<T>() or input.required<T>() (implemented in packages/core/src/authoring/input/input.ts) return InputSignal objects that provide fine-grained reactivity. Unlike decorator-based @Input properties that rely on change detection to propagate updates, signal inputs update automatically when parent bindings change without triggering full component change detection cycles.
Is EventEmitter required for @Output?
While EventEmitter is the standard implementation for @Output properties, the decorator technically accepts any object that implements the OutputRef interface. However, in practice, the Angular compiler and runtime expect the emit() method and subscription capabilities that EventEmitter provides, making it the de facto requirement for standard component communication.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →