Signal Inputs, Outputs & Models
For years a component’s public API was declared with the @Input() and @Output() decorators. Modern Angular (17.1 for inputs, 17.2 for outputs and models) replaces them with the input(), output(), and model() functions. These are not just cosmetic — inputs become read-only signals you can compute and effect over, outputs gain precise typing, and model() wires up two-way binding without boilerplate. This page covers all three, plus required inputs, aliases, and transforms.
Signal inputs with input()
Calling input() on a class field declares a reactive, read-only InputSignal. The parent still binds with the familiar square-bracket syntax, but inside the component you read the value by calling the signal like any other.
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<article class="card">
<h3>{{ name() }}</h3>
<p>{{ role() }}</p>
</article>
`,
})
export class UserCardComponent {
// optional input with a default value -> InputSignal<string>
name = input('Anonymous');
role = input('Member');
}
<app-user-card [name]="user.fullName" [role]="user.title" />
Because inputs are signals, they compose with the rest of the reactivity system. A computed() derived from an input recalculates automatically whenever the parent pushes a new value — no ngOnChanges lifecycle hook required.
import { Component, input, computed } from '@angular/core';
@Component({
selector: 'app-price-tag',
standalone: true,
template: `<span>{{ formatted() }}</span>`,
})
export class PriceTagComponent {
amount = input.required<number>();
currency = input('USD');
formatted = computed(() =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: this.currency(),
}).format(this.amount()),
);
}
Required inputs
input.required<T>() declares an input that has no default and must be supplied by the parent. Angular enforces this at compile time, so a missing binding is a build error rather than a runtime undefined.
id = input.required<string>(); // InputSignal<string>
Output:
NG8008: Required input 'id' from component UserCardComponent must be specified.
Aliases and transforms
Both options are passed via the second argument. An alias exposes a different public binding name than the field, and transform coerces the incoming value before it reaches the signal. Angular ships booleanAttribute and numberAttribute for the common cases.
import { Component, input, booleanAttribute, numberAttribute } from '@angular/core';
@Component({ selector: 'app-toggle', standalone: true, template: `` })
export class ToggleComponent {
disabled = input(false, { transform: booleanAttribute });
tabIndex = input(0, { transform: numberAttribute, alias: 'tabindex' });
}
With booleanAttribute, <app-toggle disabled> resolves to true exactly like a native HTML boolean attribute.
Outputs with output()
output() declares an OutputEmitterRef<T>. You emit values with .emit(), and the parent listens with the same (eventName) syntax as before. The big win is type inference: the payload type flows straight from the generic.
import { Component, output } from '@angular/core';
@Component({
selector: 'app-search-box',
standalone: true,
template: `<input (input)="onInput($event)" />`,
})
export class SearchBoxComponent {
search = output<string>();
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
this.search.emit(value);
}
}
<app-search-box (search)="handleQuery($event)" />
Unlike @Output(), output() is not an EventEmitter and is not an RxJS subject — it does not implement Observable. If you need a stream, convert it with outputToObservable() from @angular/core/rxjs-interop.
Two-way binding with model()
model() creates a writable ModelSignal that supports the banana-in-a-box [(value)] syntax. It pairs a signal input with a matching output named <field>Change, and the component can both read and update it.
import { Component, model } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<button (click)="dec()">-</button>
<span>{{ value() }}</span>
<button (click)="inc()">+</button>
`,
})
export class CounterComponent {
value = model(0); // ModelSignal<number>
inc() { this.value.update((v) => v + 1); }
dec() { this.value.update((v) => v - 1); }
}
<app-counter [(value)]="count" />
A change in the child flows back to the parent’s count, and a change in the parent flows down into the child — all without writing an explicit valueChange output. Use model.required<T>() when the parent must always bind.
Comparison at a glance
| Concern | Decorator API | Signal API |
|---|---|---|
| Input | @Input() name: string | name = input<string>() |
| Required input | manual assertion / { required: true } | input.required<string>() |
| Read inside class | plain property | call the signal: name() |
| Reacting to changes | ngOnChanges | computed() / effect() |
| Output | @Output() x = new EventEmitter() | x = output<T>() |
| Two-way binding | input + xChange output | x = model<T>() |
Tip: Signal inputs are read-only inside the component. Attempting to write to one (e.g.
this.name.set(...)) is a type error. Usemodel()when the component itself needs to mutate the value.
Warning: You cannot mix
@Input()andinput()for the same property, and signal inputs are not available to@ContentChild/@ViewChildqueries that rely on the decorator’s static option in the same way. Migrate a property fully rather than half-way.
Best practices
- Prefer the signal APIs for new components — they are type-safe, work with
computed()/effect(), and remove the need forngOnChanges. - Use
input.required<T>()instead of defensiveundefinedchecks; let the compiler guarantee the value is present. - Reach for
transform: booleanAttribute/numberAttributeso template attributes coerce predictably, matching native HTML behavior. - Use
model()only for genuine two-way state (form controls, toggles); for one-directional notifications, anoutput()is clearer. - Derive view state with
computed()over inputs rather than copying input values into separate fields you must keep in sync. - Run
ng generate @angular/core:signal-input-migrationand the related schematics to convert existing decorators automatically.