When code holds an Angular component instance, how do you subscribe to its output() programmatically, and when must you call unsubscribe on the OutputRefSubscription?
answer
- subscribe on the instance field
- returns OutputRefSubscription
- cleared when the component dies
- the listener's lifetime decides
basics
~10 sCall 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.
solid answer
~40 sWith a `ComponentRef` or a test fixture you call `ref.instance.rated.subscribe(cb)` on the `OutputEmitterRef`. It returns an `OutputRefSubscription` whose only method is `unsubscribe()`. The callback runs synchronously on every `emit()`. When the component is destroyed, the emitter clears all its listeners, so a subscription cannot keep a dead component's emitter firing; subscribing after destroy throws NG0953. You call `unsubscribe()` when the listener's owner is shorter-lived than the component: a panel that closes while the component stays, code that re-subscribes on every open and would otherwise stack duplicate handlers, or a one-shot listener, which can unsubscribe inside its own callback safely. For `@Output` with `EventEmitter`, `subscribe` returns an RxJS `Subscription` with the same rule.
code
ts · 9 linesimport { ComponentRef } from '@angular/core';
import { RatingStars } from './rating-stars';
export function listenOnce(ref: ComponentRef<RatingStars>, save: (n: number) => void): void {
const sub = ref.instance.rated.subscribe((stars) => {
sub.unsubscribe();
save(stars);
});
}go deeper
Know that code holding a component instance can call subscribe on its output and get back an object with unsubscribe().
Explain that Angular clears output listeners on component destroy, and that unsubscribe() matters when the listener's owner is shorter-lived.
Find duplicate or stale handlers caused by repeated programmatic subscriptions, and structure hosts so listener and component lifetimes line up.
Standardise how plugin-style hosts wire outputs, preferring lifetime-managed bindings over ad hoc subscriptions to reduce leak-prone code.
## When there is no template binding Most outputs are consumed with a template binding such as `(rated)="save($event)"`, and Angular manages that listener completely. But some code holds a component **instance** rather than a template: - a host that creates the component from code and gets a `ComponentRef`; - a test that creates it with `TestBed.createComponent`; - a directive or service that is handed a component instance. That code listens by calling **`subscribe`** on the output field. ## Subscribing to an output() `OutputEmitterRef.subscribe(callback)` registers a listener and returns an **`OutputRefSubscription`**, an object with a single method, `unsubscribe()`: ```ts import { ComponentRef } from '@angular/core'; import { RatingStars } from './rating-stars'; export function listenToRating(ref: ComponentRef<RatingStars>, save: (n: number) => void) { const sub = ref.instance.rated.subscribe((stars) => save(stars)); return () => sub.unsubscribe(); } ``` Key behaviours, all from the emitter's implementation: - The callback runs **synchronously** each time the component calls `emit()`. - `unsubscribe()` removes that one callback; calling it during an `emit()` is safe, because the emitter nulls the slot and compacts its list afterwards. - When the **component is destroyed**, the emitter drops *all* listeners automatically. The docs say Angular cleans up these subscriptions when it destroys the component. - Subscribing to an output of a component that is **already destroyed** throws **NG0953**. ## So when must you call unsubscribe()? Because destruction clears listeners, you do **not** need `unsubscribe()` just to avoid leaking the component. You need it when **the listener's owner goes away first** and the component keeps living: | Situation | Unsubscribe needed? | |---|---| | Listener lives as long as the component, e.g. its creating host destroys both together | no | | Test that creates, asserts and lets TestBed tear down | no | | A short-lived panel listens to a long-lived component's output, then closes | **yes** | | The same code subscribes again on every re-open | **yes**, or listeners pile up and fire repeatedly | | Listener only wants the first event | yes, unsubscribe inside the callback | The failure modes when you forget are: 1. **Stale callbacks run.** A closed panel's handler still fires, often against state that no longer exists. 2. **Duplicate calls.** Each re-subscription adds another listener, so one emit triggers the handler several times. 3. **Memory retention.** The component's emitter holds the callback, and the callback holds whatever it closes over. ## A one-shot listener ```ts const sub = ref.instance.rated.subscribe((stars) => { sub.unsubscribe(); save(stars); }); ``` This works even though `unsubscribe()` runs while `emit()` is iterating, thanks to the null-slot handling. ## Decorator outputs For an `@Output()` backed by `EventEmitter`, `subscribe` is the RxJS `Subject.subscribe` and returns an RxJS `Subscription`, which also has `unsubscribe()`. The same ownership rule applies: unsubscribe when the listener's owner is shorter-lived than the component. ## Alternatives worth naming - When creating a component from code, Angular also offers binding helpers that wire outputs at creation time; they are managed with the component's lifetime. That creation API is a separate subject. - When the consumer is RxJS-based, dedicated interop functions convert an output into an Observable, again a separate subject. ## The interview point The candidate should know that `output()` subscriptions return `OutputRefSubscription`, that Angular drops them when the component is destroyed, and that `unsubscribe()` is about the **listener's** lifetime, not the component's.
- Is it safe to call unsubscribe() from inside the listener while emit() is running?Yes. `OutputEmitterRef` detects that it is mid-emit, replaces the listener's slot with null instead of splicing the array, and removes the null entries after the loop, so other listeners are neither skipped nor called twice.
- What happens if code subscribes to an output after its component was destroyed?`subscribe()` throws NG0953, because the emitter was marked destroyed and will never emit again. This usually reveals code holding a stale `ComponentRef`; check the component's lifetime before subscribing.
An output subscription is like a mailing list kept by the sender: when the sender closes down, the list is shredded and nobody needs to opt out, but if you move away first you must unsubscribe yourself or the letters keep arriving at an empty house.
saying these in an interview costs you the question
- Every programmatic output subscription leaks the component unless unsubscribed.
- subscribe() on an output() returns an RxJS Subscription.
- Listeners keep firing after the component is destroyed.
- Unsubscribing inside the callback breaks the current emit loop.
- Re-subscribing on every open is harmless because duplicates are merged.