In Angular, how does the OutputEmitterRef returned by output() differ from an EventEmitter used with @Output, and what should you check when migrating?
answer
- a Subject versus a minimal emitter
- subscribe returns OutputRefSubscription
- cleaned up with the component
- EventEmitter(true) was asynchronous
basics
~20 sEventEmitter 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.
solid answer
~30 s`EventEmitter` extends RxJS `Subject`, so it exposes `pipe`, `next`, `error` and `complete`, returns an RxJS `Subscription`, and delivers asynchronously if created with `new EventEmitter(true)`. `output()` returns an `OutputEmitterRef`, which only has `emit` and `subscribe`; `subscribe` returns an `OutputRefSubscription` with `unsubscribe()`. It is lifecycle-aware: on destroy it drops all listeners, ignores later emits with a dev warning, and throws on new subscriptions (NG0953). A listener that throws is reported to `ErrorHandler` without stopping the others. When migrating, parents need no change, but rewrite any code that piped the emitter as an Observable and check handlers that relied on async delivery.
go deeper
Know that output() returns an OutputEmitterRef and @Output uses an EventEmitter, and that both are fired with emit().
Contrast the two: RxJS Subject versus minimal emitter, subscription types, sync versus optional async delivery, and destroy behaviour.
Migrate EventEmitter outputs safely, finding code that used them as Observables or relied on async delivery, and keep tests passing.
Weigh removing RxJS from component contracts across a codebase against the cost of rewriting stream-based output code.
## Two emitter types Angular has two objects behind a component output: - **`OutputEmitterRef<T>`**, returned by the `output()` function. It is a small, purpose-built class in `@angular/core` with two methods: `emit(value)` and `subscribe(callback)`. - **`EventEmitter<T>`**, used with the `@Output()` decorator. It **extends the RxJS `Subject`** and adds an `emit()` method that calls `next()`. Both implement Angular's `OutputRef<T>` interface, which is why a parent's template binding works the same for either. ## Where they differ | | `OutputEmitterRef` (`output()`) | `EventEmitter` (`@Output()`) | |---|---|---| | Base type | Angular class, no RxJS | RxJS `Subject` | | Methods | `emit`, `subscribe` | `emit`, plus the full Observable/Subject API: `pipe`, `next`, `error`, `complete` | | `subscribe()` returns | `OutputRefSubscription` with `unsubscribe()` | an RxJS `Subscription` | | Delivery | always synchronous | synchronous, or async if built with `new EventEmitter(true)` | | A listener throws | the error goes to Angular's `ErrorHandler`; remaining listeners still run | RxJS `Subject` error semantics apply | | On component destroy | listeners dropped automatically | template listeners are cleaned up by Angular; manual subscriptions are yours | | `emit()` after destroy | ignored, with a dev warning (NG0953) | still calls `next()` on the subject | | `subscribe()` after destroy | throws NG0953 | allowed | | Template binding and `$event` | identical | identical | ## Why output() was introduced `EventEmitter` exposes much more than an output needs. Because it is a `Subject`, code could `pipe()` it, call `error()` or `complete()` on it, or subscribe in ways that outlive the component, and it ties every component with an output to RxJS. `OutputEmitterRef` keeps the contract minimal: 1. **Only emit and subscribe.** There is nothing to complete or error, so no consumer can end the stream. 2. **Lifecycle-aware.** It captures the component's `DestroyRef` when created. On destroy it marks itself destroyed and drops all listeners, so a subscriber cannot keep a destroyed component's emitter alive, and late emits are harmless no-ops. 3. **Error isolation.** Each listener runs in its own `try/catch`; an exception is reported to `ErrorHandler` and does not stop the other listeners. 4. **No RxJS requirement.** Components can have outputs without importing any RxJS type. ## Emitting from reactive code Both emitters run their listeners **untracked** with respect to signals: `emit()` clears the active reactive consumer before calling listeners. Emitting from inside an `effect()` therefore does not make the effect depend on whatever the listeners read. ## Converting between the two worlds When a component already has an Observable and wants to expose it as an output, or a consumer wants an output as an Observable, Angular provides dedicated interop functions in `@angular/core/rxjs-interop`. That is a separate subject; the point here is that `OutputEmitterRef` itself is **not** an Observable, so `this.rated.pipe(...)` does not compile. ## Migrating Parents do not change: `(rated)="save($event)"` works for both. In the child: ```ts import { Component, EventEmitter, Output, output } from '@angular/core'; @Component({ selector: 'app-rating-stars', template: '' }) export class RatingStars { // Before @Output() ratedLegacy = new EventEmitter<number>(); // After readonly rated = output<number>(); } ``` Things to check while migrating: - code that used the `EventEmitter` as a `Subject`, for example `this.rated.pipe(debounceTime(200))` or `subscribe` with an observer object, must be rewritten; - `new EventEmitter(true)` users relied on **asynchronous** delivery; `output()` is always synchronous, so timing-sensitive handlers may behave differently; - tests that subscribed to `componentInstance.rated` keep working, because `subscribe(callback)` exists on both, but the return type changes from `Subscription` to `OutputRefSubscription`. The Angular team recommends `output()` for new code and keeps `@Output` fully supported, so both appear side by side in real projects.
- What happens if a destroyed component's OutputEmitterRef.emit() is called from a pending timer?Nothing is delivered. On destroy the emitter marks itself destroyed and clears its listeners; a later `emit()` returns early and logs a warning with code NG0953 in development mode. Subscribing after destroy is stricter and throws the same error.
- If one listener of an output throws, do the other listeners still run?With `OutputEmitterRef`, yes. `emit()` wraps each listener call in its own try/catch and hands errors to Angular's `ErrorHandler`, then continues with the next listener.
saying these in an interview costs you the question
- OutputEmitterRef is an RxJS Observable you can pipe.
- output() delivers events asynchronously by default.
- Parents must change their bindings when a child migrates to output().
- An OutputEmitterRef keeps its listeners after the component is destroyed.
- EventEmitter is always synchronous and has no async option.