skip to content

Outputs & Custom Events

Custom events via output() and OutputEmitterRef or @Output with EventEmitter, their aliases and $event payloads. Interviewers ask why these events never bubble through the DOM.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular, how does a child component emit a custom event with output() or @Output and EventEmitter, and how does the parent read its payload?

level: juniorimportance: must knowfreq 74%

answer

  1. declare, then emit
  2. OutputEmitterRef or EventEmitter
  3. parent binds with parentheses
  4. $event is the emitted value

basics

~10 s

The child declares rated = output<number>() or @Output() rated = new EventEmitter<number>() and calls rated.emit(value). The parent binds (rated)="save($event)", where $event is exactly the emitted value, not a DOM event.

solid answer

~40 s

The recommended API is `output()`: `rated = output<number>()` returns an `OutputEmitterRef<number>`, and the child calls `this.rated.emit(3)`. The older `@Output() rated = new EventEmitter<number>()` still works and is fired the same way. The parent listens with an event binding on the child's element, `(rated)="saveScore($event)"`, and `$event` is the value passed to `emit()`, so here a number, typed by the output under strict template checking. The handler runs synchronously inside `emit()`. Both APIs accept an alias for the template name, and the docs recommend camelCase names without an `on` prefix and without colliding with DOM event names. Parents cannot tell which API the child used.

code

ts · 34 lines
ts
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-rating-stars',
  template: `
    @for (star of stars; track star) {
      <button type="button" (click)="choose(star)" [class.filled]="star <= value()">
        {{ star }}
      </button>
    }
  `,
})
export class RatingStars {
  readonly value = input(0);
  readonly rated = output<number>();
  protected readonly stars = [1, 2, 3, 4, 5];

  protected choose(star: number) {
    this.rated.emit(star);
  }
}

@Component({
  selector: 'app-product-review',
  imports: [RatingStars],
  template: `<app-rating-stars [value]="score" (rated)="saveScore($event)" />`,
})
export class ProductReview {
  score = 0;

  saveScore(stars: number) {
    this.score = stars;
  }
}

go deeper

for a junior

Declare an output with output() or @Output, fire it with emit(), and read the payload as $event in the parent's binding.

for a middle

Explain that emit() calls listeners synchronously, how aliases work, and how $event is typed under strict template checking.

for a senior

Design event names and payloads for a shared component API: camelCase, no on prefix, no DOM-name collisions, and payloads that are stable contracts.

for a principal

Decide how a large codebase moves from EventEmitter to output(), knowing parents are unaffected, and where RxJS-shaped outputs still earn their place.

## What an output is In Angular, an **output** is a custom event that a component (or directive) declares so that the component using it can react. Data flows *into* a component through inputs and *out* of it through outputs. There are two ways to declare one. **The `output()` function** from `@angular/core` is the current recommendation. It was introduced in v17.3 and marked stable in v19: ```ts import { Component, output } from '@angular/core'; @Component({ selector: 'app-rating-stars', template: '' }) export class RatingStars { readonly rated = output<number>(); // OutputEmitterRef<number> protected choose(star: number) { this.rated.emit(star); } } ``` **The `@Output()` decorator** on an `EventEmitter` field is the original API, still fully supported and very common in existing code: ```ts import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-legacy-rating-stars', template: '' }) export class LegacyRatingStars { @Output() rated = new EventEmitter<number>(); } ``` In both cases the child fires the event by calling **`emit(value)`**. ## How the parent listens The parent binds to the output with the event-binding syntax on the child's element, exactly as it would bind to a native event: ```html <app-rating-stars [value]="score" (rated)="saveScore($event)" /> ``` - **`$event`** is the **value passed to `emit()`**, here the number of stars. It is *not* a DOM `Event` object, so there is no `$event.target`. - The handler expression runs **synchronously** inside `emit()`: by the time `emit()` returns, `saveScore` has run. - `output<void>()` declares an event with no payload, fired with `emit()`. - With strict template checking, `$event` is typed from the output's type parameter, so `saveScore(stars: number)` is checked. ## The full component The rating-stars component renders five buttons and emits the chosen value: ```ts import { Component, input, output } from '@angular/core'; @Component({ selector: 'app-rating-stars', template: ` @for (star of stars; track star) { <button type="button" (click)="choose(star)">{{ star }}</button> } `, }) export class RatingStars { readonly value = input(0); readonly rated = output<number>(); protected readonly stars = [1, 2, 3, 4, 5]; protected choose(star: number) { this.rated.emit(star); } } ``` Note that the component does **not** change `value` itself: it reports the choice, and the parent decides whether to store it and bind it back. (A component that should own a two-way value uses a model input instead, a separate subject.) ## Aliases and naming Both APIs accept an alias, the name used in templates: | API | Alias syntax | Template | |---|---|---| | `output()` | `rated = output<number>({ alias: 'ratingChosen' })` | `(ratingChosen)="..."` | | `@Output()` | `@Output('ratingChosen') rated = new EventEmitter<number>()` | `(ratingChosen)="..."` | The Angular docs' naming guidance: 1. Use **camelCase** names. 2. Do **not** prefix outputs with `on` (`onRated` reads as a handler, not an event). 3. Avoid names that **collide with DOM events** such as `click` or `change`. 4. Avoid aliases in general; they help when renaming while keeping an old public name. Like `input()`, `output()` may only be called in a **field initializer** of a component or directive; the compiler records outputs statically. ## output() versus @Output at a glance | | `output()` | `@Output()` + `EventEmitter` | |---|---|---| | Returned object | `OutputEmitterRef<T>` | `EventEmitter<T>`, an RxJS `Subject` | | Fire | `emit(value)` | `emit(value)` | | Depends on RxJS | no | yes | | Parent syntax | `(rated)="f($event)"` | same | Parents cannot tell which API a child used: the binding is identical, which is why codebases can migrate one component at a time. ## What interviewers listen for - `emit()` to fire, `(name)="handler($event)"` to listen. - `$event` is the emitted payload. - `output()` is the recommended API; `@Output` with `EventEmitter` is still supported. - Outputs are delivered to the listener bound on that element, not broadcast through the page.

  • What does $event contain when the parent binds (rated)="saveScore($event)"?
    Exactly the value the child passed to `emit()`, here a number. Angular does not wrap it in an event object, so there is no `target` or `preventDefault()`. Only bindings to native DOM events give `$event` an `Event` object.
  • How do you declare an output that carries no payload?
    Use `output<void>()` and call `emit()` with no argument; `output()` without a type argument also defaults to `void`. The parent binds `(closed)="onClosed()"` and simply ignores `$event`.
  • Is emit() synchronous?
    Yes. `OutputEmitterRef.emit()` calls every registered listener in a loop before returning, so the parent's handler has run by the time the next line in the child executes. An `EventEmitter` is also synchronous unless it was constructed with `new EventEmitter(true)`, which delivers asynchronously.

saying these in an interview costs you the question

  • $event on a component output is always a DOM Event with a target.
  • The child must call dispatchEvent to raise an output.
  • output() returns an RxJS Observable you can pipe.
  • The @Output decorator is deprecated and no longer works.
  • Output names should start with on, like onRated.
open as a page

In Angular, how does the OutputEmitterRef returned by output() differ from an EventEmitter used with @Output, and what should you check when migrating?

level: middleimportance: should knowfreq 45%

basics

~20 s

EventEmitter is an RxJS Subject with pipe, error and complete, optionally asynchronous. OutputEmitterRef only offers emit and subscribe, is always synchronous, drops its listeners when the component is destroyed, and needs no RxJS. Parent bindings are identical.

open as a page

An Angular rating component declares an output named change; why does the parent's (change) handler run twice, once receiving a DOM Event instead of the rating?

level: seniorimportance: should knowfreq 30%

basics

~20 s

An event binding on a component's element subscribes to its matching output and also adds a native DOM listener. The inner radio's native change bubbles to the host, so the handler runs with the number, then with the Event.

open as a page

When code holds an Angular component instance, how do you subscribe to its output() programmatically, and when must you call unsubscribe on the OutputRefSubscription?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

Call instance.rated.subscribe(callback), which returns an OutputRefSubscription. Angular drops the listener when the component is destroyed, so call unsubscribe() only when the listener's owner goes away first or subscribes repeatedly.

open as a page