All topics
Stateintermediate

Signal-based Inputs and Outputs

Explain the input()/output() functions as the signal-based alternative to @Input()/@Output() decorators.

Angular 17.1+ introduced input() and Angular 17.3+ introduced output() as signal-based alternatives to the decorator-based @Input()/@Output() API, and they matter for interviews because they represent the direction Angular's component API is actively moving — integrating inputs directly into the Signal reactivity graph rather than sitting alongside it as a separate mechanism.

The old @Input()/@Output() decorators were like a separate mailroom that had to forward messages to the rest of the office manually (ngOnChanges); signal inputs/outputs plug directly into the same internal messaging system (the signal graph) everything else in the component already uses, so nothing needs a special hand-off step anymore.

Key Concepts

1
A signal input, declared as value = input<number>(0) (with a default) or value = input.required<number>() (mandatory, enforced by the type system — accessing it before Angular has set it is a compile-time impossibility, not just a runtime risk), is read exactly like any other Signal, by calling it as a function. This means a signal input can be fed directly into a computed() or watched by an effect() using the exact same dependency-tracking mechanism as any other Signal, without needing ngOnChanges at all to react to changes — a computed() that reads a signal input automatically recalculates whenever that input changes, which is a much more direct replacement for what ngOnChanges used to be needed for.
value = input<number>(0)value = input.required<number>()computed()effect()ngOnChanges
2
output() is a lighter-weight replacement for @Output() = new EventEmitter(): it's created via value = output<T>(), emits through the same .emit(value) method call, and is still consumed identically from a parent template via the parenthesis event-binding syntax — but it's no longer literally an EventEmitter/Subject wrapper the way the decorator-based version was, which slightly simplifies its internal type and semantics.
output()@Output() = new EventEmitter()value = output<T>().emit(value)EventEmitter
3
A thoughtful interview answer notes that input()/output() don't replace @Input()/@Output() outright — both are fully supported and can coexist in the same codebase — but new code, especially in components that otherwise lean on signals for internal state, benefits from using the signal-based versions throughout for a more consistent, uniformly-reactive component model.
input()output()@Input()@Output()