Inputs and outputs

Angular · Essentials · 12 min

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 signals
  • output<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.

Check yourself

  1. A child declares `closed = output<string>()` and calls this.closed.emit('esc'). How does the parent receive 'esc'?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: angular.dev/guide/components/inputs