Skip to content
Angular ng components 4 min read

Output Events

While inputs let data flow into a component, outputs let a child component notify its parent that something happened — a button was clicked, a value changed, a row was selected. Angular models this with the @Output() decorator paired with an EventEmitter, giving you a clean, typed, one-way channel that flows from child to parent. This keeps components decoupled: the child announces what happened without knowing or caring who is listening.

Declaring an output

An output is a public class property decorated with @Output() and assigned a new EventEmitter. The generic type parameter declares the shape of the payload the event carries. The child calls .emit(value) to fire the event, and the parent subscribes to it in the template using the familiar (eventName) event-binding syntax.

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <button (click)="increment()">Clicked {{ count }} times</button>
  `,
})
export class CounterComponent {
  count = 0;

  @Output() countChange = new EventEmitter<number>();

  increment(): void {
    this.count++;
    this.countChange.emit(this.count);
  }
}

The parent listens for countChange and reacts to the emitted number:

import { Component } from '@angular/core';
import { CounterComponent } from './counter.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CounterComponent],
  template: `
    <app-counter (countChange)="onCount($event)" />
    <p>Last reported count: {{ latest }}</p>
  `,
})
export class DashboardComponent {
  latest = 0;

  onCount(value: number): void {
    this.latest = value;
  }
}

The $event variable in the template holds whatever value the child passed to .emit(). Because the emitter is typed EventEmitter<number>, $event is strongly typed as number, and the compiler will flag a mismatch if onCount expects something else.

Tip: EventEmitter extends RxJS Subject, but treat outputs purely as an event channel. Do not subscribe to them imperatively from a parent or rely on their observable nature — that is an implementation detail Angular reserves the right to change.

Emitting rich payloads

Outputs are not limited to primitives. A common pattern is emitting a typed object or a discriminated union describing the action that occurred. This is especially useful for list or table components where the parent needs context about which item triggered the event.

import { Component, Output, EventEmitter } from '@angular/core';

interface Product {
  id: string;
  name: string;
}

@Component({
  selector: 'app-product-row',
  standalone: true,
  template: `
    <div class="row">
      <span>{{ product.name }}</span>
      <button (click)="select.emit(product)">Buy</button>
      <button (click)="remove.emit(product.id)">Remove</button>
    </div>
  `,
})
export class ProductRowComponent {
  product: Product = { id: 'p1', name: 'Keyboard' };

  @Output() select = new EventEmitter<Product>();
  @Output() remove = new EventEmitter<string>();
}

You can call .emit() directly in the template, as shown above, or route through a method when there is logic to run first. Keep template-side .emit() calls to trivial pass-throughs.

The two-way binding convention

Angular has a special convention: if an input is named value and a matching output is named valueChange, consumers can use the banana-in-a-box [(value)] syntax for two-way binding. This is exactly how ngModel works under the hood.

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-toggle',
  standalone: true,
  template: `
    <button (click)="toggle()">{{ checked ? 'On' : 'Off' }}</button>
  `,
})
export class ToggleComponent {
  @Input() checked = false;
  @Output() checkedChange = new EventEmitter<boolean>();

  toggle(): void {
    this.checked = !this.checked;
    this.checkedChange.emit(this.checked);
  }
}

A parent can now write <app-toggle [(checked)]="isEnabled" />, which Angular desugars to [checked]="isEnabled" (checkedChange)="isEnabled = $event".

Renaming and aliasing outputs

The @Output() decorator accepts an optional alias — the public binding name used in templates — while the class property keeps its internal name. Use aliasing sparingly, since it adds an indirection that can confuse readers.

@Output('itemSelected') select = new EventEmitter<Product>();

Consumers bind to (itemSelected) even though the field is select. The general guidance is to keep the alias and the property name identical unless you have a strong reason.

How outputs differ from other communication

MechanismDirectionBest for
@Input()Parent → childPassing data down
@Output()Child → parentNotifying of discrete events
Shared serviceAny → anySibling / cross-tree state
Signals (output())Child → parentModern signal-based components

In Angular 17.3+ the function-based output() API supersedes the decorator for new code. It is fully covered on the signal inputs and outputs page; the @Output() decorator described here remains fully supported and is what you will see in the majority of existing codebases.

Best practices

  • Name outputs as past-tense events (saved, deleted, selectionChanged) rather than commands — the child reports what happened, the parent decides what to do.
  • Always declare the generic type on EventEmitter<T> so payloads are strongly typed; avoid EventEmitter<any>.
  • Emit a meaningful payload, not a bare signal — give the parent the context it needs (the changed value, the affected entity, or its id).
  • Follow the xxx / xxxChange naming pair only when you genuinely want two-way binding; otherwise pick a descriptive name.
  • Do not pass EventEmitter instances into a child or subscribe to outputs imperatively — they are a template-binding API, not a general event bus.
  • Prefer the newer output() function for greenfield components to align with the signals direction of the framework.
Last updated June 14, 2026
Was this helpful?