In RxJS, what makes a Subject both an Observable and an Observer, and how does that let one source execution feed many subscribers?
answer
- subscribe on one side, next on the other
- a list of observers
- pass the subject to subscribe()
- one execution, many listeners
- values before subscription are lost
basics
~20 sA Subject has subscribe() like an Observable and next(), error() and complete() like an Observer, keeping a list of subscribers; subscribing it to a source runs that source once and rebroadcasts every value to all of them.
solid answer
~40 sA plain `Observable` is **unicast**: each `subscribe()` runs its producer again for that subscriber alone. A `Subject` keeps a list of observers; `subscribe()` adds to the list, and `next(v)` loops over the list delivering `v` to each — so it is **multicast**. Because it is also an Observer, you can hand it to a source: `source$.subscribe(subject)` runs the source **once** and the subject fans every value, error and completion out to all its subscribers. Two consequences: a value pushed while nobody is subscribed is lost, and once the subject has completed or errored it ignores further `next()` calls and sends new subscribers only that terminal notification. `share()` packages this wiring for you.
code
ts · 10 linesimport { Subject } from 'rxjs';
const refresh = new Subject<void>();
// Consumers get a read-only view: they can subscribe but not call next()
export const refresh$ = refresh.asObservable();
export function requestRefresh(): void {
refresh.next();
}go deeper
Recall that a Subject can be subscribed to and pushed into, and that every subscriber receives each pushed value.
Explain unicast versus multicast in RxJS terms, how source$.subscribe(subject) shares one execution, and the stopped state after complete() or error().
Choose between a hand-held subject and share() or connectable() by who owns the source lifecycle, and keep the observer side private with asObservable().
Limit where subjects may be created and exposed across a codebase so push access stays in one owner and every other consumer only reads.
## Two roles in one object RxJS separates two roles: - An **Observable** is something you can `subscribe` to. Each subscription runs the Observable's producer function for that subscriber. - An **Observer** is something that receives notifications through three methods: `next(value)`, `error(err)` and `complete()`. A **`Subject`** plays both roles at once: 1. It has `subscribe()`, so consumers can subscribe to it like any Observable. 2. It has `next()`, `error()` and `complete()`, so anything that expects an Observer — including another Observable's `subscribe()` — can push into it. 3. Internally it keeps a **list of current observers**. `subscribe()` adds one to the list; `next(v)` delivers `v` to every observer on the list. ## Unicast versus multicast in RxJS terms A plain Observable is **unicast**. Its producer runs once **per subscription**: - Two subscribers to `interval(1000)` get two independent timers. - Two subscribers to an observable that sends a request trigger two requests. A `Subject` is **multicast**. Subscribing to it runs nothing; it just adds you to a list. Every observer on the list receives the same values from the same `next()` calls. The bridge between the two is one line: pass the subject **as the observer** of a source. ```ts import { Subject, interval, take, tap } from 'rxjs'; const source$ = interval(1000).pipe( take(3), tap((i) => console.log('producer ran', i)), ); const hub = new Subject<number>(); hub.subscribe((v) => console.log('A', v)); hub.subscribe((v) => console.log('B', v)); source$.subscribe(hub); // producer ran 0, A 0, B 0 // producer ran 1, A 1, B 1 // producer ran 2, A 2, B 2 (one timer, two listeners; completion reaches both) ``` `source$` runs once; the subject forwards its values, and its completion, to both `A` and `B`. ## Rules that follow from the design - **No memory.** A value passed to `next()` while nobody is subscribed is delivered to nobody. Subscribers must be in place before the source starts, or you need a replaying subject. - **Terminal state is permanent.** After `complete()` or `error()`, the subject is stopped: later `next()` calls are silently ignored, and a new subscriber immediately receives the completion or the error. - **Order of delivery** follows subscription order, synchronously, inside the `next()` call. - **Closing is different from completing.** Calling `unsubscribe()` on the subject itself closes it, and any later `next()` or `subscribe()` throws an `ObjectUnsubscribedError`. Consumers should unsubscribe their own `Subscription` instead. - **Hide the observer side.** Exposing a subject lets any consumer call `next()` on it. `subject.asObservable()` returns a plain Observable view with only `subscribe`, so outside code can listen but not push. ## The variants | Class | What a new subscriber gets | |---|---| | `Subject` | only future notifications | | `BehaviorSubject` | the current value, then future ones | | `ReplaySubject` | buffered past values, then future ones | | `AsyncSubject` | only the final value, on completion | ## From hand-wiring to operators Wiring `source$.subscribe(subject)` by hand works, but you must decide when to start the source and when to stop it. The operators do this bookkeeping: - **`share()`** creates the subject on the first subscriber, subscribes it to the source, and unsubscribes when the last subscriber leaves. - **`connectable(source$)`** returns an observable that starts the source only when you call its `connect()` method, so all subscribers can be attached first. A hand-held subject is still the right tool when **your own code** produces the values — for example a click handler calling `clicks.next(event)`.
- What happens to next() calls on a Subject after complete() has been called?They are ignored silently: the subject is stopped and delivers nothing. A new subscriber receives the completion immediately. This is different from calling `unsubscribe()` on the subject, which closes it so that later `next()` or `subscribe()` calls throw an `ObjectUnsubscribedError`.
- Why would you prefer share() over subscribing a Subject to a source by hand?`share()` manages the lifecycle: it subscribes the source when the first subscriber arrives, unsubscribes when the last one leaves, and resets after completion or error so the stream can start again. Hand-wiring means you must decide when to call `source$.subscribe(subject)`, keep that subscription, and tear it down yourself — easy to leak or start too early.
saying these in an interview costs you the question
- Subscribing to a Subject runs its source again for each subscriber
- A plain Subject buffers values until the first subscriber arrives
- Calling next() after complete() throws an error
- Two subscribers to a plain Observable always share one execution
- asObservable() copies the values, so consumers get a separate stream