Inputs and outputs define a component's public API and how parents and children communicate.
Components receive data through inputs and notify parents through outputs.
Inputs are declared with the input() function, which returns a read-only signal:
name = input.required<string>(); // parent must provide it
size = input(48); // optional with default
disabled = input(false, { transform: booleanAttribute });The parent binds with brackets, <app-avatar [name]="user().name" [size]="64" />, or as a static string for strings. Because inputs are signals, you can derive from them with computed(() => this.size() * 2). Required inputs can't be read before Angular sets them, so don't read them in the constructor; read them in templates, computeds or lifecycle hooks. Transforms like booleanAttribute let <app-x disabled /> work as true.
Outputs are declared with output():
selected = output<User>();
this.selected.emit(user);The parent listens with event syntax: (selected)="onSelect($event)", where $event is the emitted value. Output events don't bubble through the DOM.
Use alias options sparingly, keep inputs immutable (the child never writes to them; input() signals have no set), and name outputs after what happened (selected, closed), not after what the parent should do. Older code uses @Input() and @Output() decorators with EventEmitter; they still work.
Example
import { Component, input, output, computed, booleanAttribute } from '@angular/core';
export type User = { id: number; name: string };
@Component({
selector: 'app-user-row',
template: `<span [style.font-size.px]="fontSize()">{{ user().name }}</span>
<button (click)="remove.emit(user().id)" [disabled]="readonly()">x</button>`,
})
export class UserRow {
user = input.required<User>();
size = input<'sm' | 'lg'>('sm');
readonly = input(false, { transform: booleanAttribute });
remove = output<number>();
protected fontSize = computed(() => (this.size() === 'lg' ? 20 : 14));
}
@Component({
selector: 'app-user-list',
imports: [UserRow],
template: `@for (u of users; track u.id) {
<app-user-row [user]="u" size="lg" (remove)="onRemove($event)" /> }`,
})
export class UserList {
users: User[] = [{ id: 1, name: 'Ada' }];
onRemove(id: number) { console.log('remove', id); } // remove 1
}Remember
input()/input.required<T>()declare inputs as read-only signalsoutput<T>()declares events;emit(value)sends them, parents get$event- Don't read required inputs in the constructor
Watch out for
- Reading a required input in the constructor, which throws because it hasn't been set yet.