In Angular, what does `outputFromObservable()` from `@angular/core/rxjs-interop` do, and how does it handle subscription, errors and completion?
answer
- an output backed by a stream
- field initializer only
- subscribes per listener
- errors are yours to catch
- completion ends emissions; destroy stops forwarding
basics
~10 sAngular's outputFromObservable() declares a component or directive output whose events come from an Observable; each listener's subscription is forwarded to the source, errors are not handled, completion stops emissions, and forwarding stops on destroy.
solid answer
~40 s`outputFromObservable(source$)` (stable since v19) is called in a **field initializer** of a component or directive; the compiler treats it like `output()`, so parents bind `(priceAlert)="..."` as usual. It does not subscribe on its own: when a listener subscribes to the output, Angular **forwards that subscription** to the source, piped through `takeUntilDestroyed`, so values stop when the component is destroyed. Each listener gets its own subscription, so a cold source runs once per listener. **Errors are not handled** by Angular, so add `catchError` to the source; **completion** stops the output from emitting. Subscribing to the output after the component is destroyed throws `NG0953`. Prefer plain `output()` when you emit imperatively; `outputToObservable()` goes the other way.
code
ts · 18 linesimport { Component, inject } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';
import { EMPTY, catchError, filter, map } from 'rxjs';
import { PriceFeed } from './price-feed';
@Component({ selector: 'app-price-watch', template: '' })
export class PriceWatch {
private readonly feed = inject(PriceFeed);
// Field initializer; errors are handled in the source because Angular will not.
readonly priceAlert = outputFromObservable(
this.feed.quotes$.pipe(
filter(q => q.price > 100),
map(q => q.symbol),
catchError(() => EMPTY),
),
);
}go deeper
Know that outputFromObservable declares an output from an Observable, called in a field initializer, and that parents bind it like any other output.
Explain the forwarded, per-listener subscription with takeUntilDestroyed, and that errors must be caught in the source while completion ends emissions.
Watch for cold sources that run once per listener, and choose between output() and outputFromObservable() by where the event originates.
Set guidance on when component outputs should wrap streams versus emit imperatively, keeping public component APIs simple for consumers.
## What it is for Most outputs are declared with `output()` and fired imperatively with `.emit(value)`. Sometimes the event already exists as an **Observable**: a filtered price stream, pointer moves, a debounced search term. `outputFromObservable()` turns that Observable into a real Angular output without a manual `subscribe` that calls `emit`. ```ts @Component({ selector: 'app-price-watch', template: '' }) export class PriceWatch { private readonly feed = inject(PriceFeed); readonly priceAlert = outputFromObservable( this.feed.quotes$.pipe(filter(q => q.price > 100)), ); } ``` A parent binds it like any output: `<app-price-watch (priceAlert)="notify($event)" />`. ## Where it may be called The function has **special meaning to the Angular compiler**, which recognises it as an output declaration. Angular's guide states it may only be called in **component and directive property initializers**. At runtime it also injects `DestroyRef`, and in development mode it asserts an injection context. ## How subscription works `outputFromObservable` returns an `OutputRef`. Nothing subscribes to the source when the component is created. Instead: 1. A listener (a template event binding, or code calling `.subscribe()` on the output) subscribes to the `OutputRef`. 2. The `OutputRef` subscribes to the **source**, piped through `takeUntilDestroyed` with the component's `DestroyRef`. 3. Each source value is passed to that listener's callback. Consequences: | Situation | Behaviour | |---|---| | no listener bound | the source is never subscribed | | two listeners bound | two source subscriptions; a cold source runs twice | | component destroyed | forwarding stops for every listener | | subscribe after destroy | throws `NG0953`, an unexpected subscription to a destroyed `OutputRef` | ## Errors and completion Angular documents three rules for the source: - **`next`** notifications become output events. - **Errors are not handled** by Angular. The forwarding subscription has no error callback, so an error is an unhandled RxJS error and the output stops. Handle it in the source, for example with `catchError` mapped to a safe value or to `EMPTY`. - **Completion** stops the output from emitting new values. ## The reverse bridge `outputToObservable(ref)` (also stable since v19) turns an output, from `output()` or `outputFromObservable()`, into an `Observable`, which is useful when a parent holds a component instance and wants RxJS operators on its events. Angular's guide notes that calling `subscribe` on the `OutputRef` directly is often enough. ## When to use which - The event is produced by **your code** at specific moments: use `output()` and `emit()`. - The event **is** an existing stream: use `outputFromObservable()`. - You need to **consume** an output as a stream: use `outputToObservable()`. ## What interviewers listen for - **Field initializer only**; the compiler treats it as an output. - **Lazy, per-listener subscription** with `takeUntilDestroyed`. - **Errors must be caught in the source**; completion ends emissions. - `output()` remains the default for imperative events.
- Does an output created with outputFromObservable subscribe to its source when no parent listens?No. The `OutputRef` forwards subscriptions: the source is subscribed only when something subscribes to the output, such as a parent's event binding. With no listener, a cold source never runs.
- Why would two listeners on such an output cause two WebSocket or HTTP subscriptions?Each listener's subscription is forwarded separately to the source Observable. If the source is cold, each forwarded subscription runs it again. Share the source upstream, or bind one listener, when the work must happen once.
saying these in an interview costs you the question
- outputFromObservable subscribes to the source as soon as the component is created
- Angular forwards source errors to the parent as an error event
- outputFromObservable can be called in ngOnInit
- Two listeners on the output share one source subscription
- The output keeps emitting after the component is destroyed