In a zoneless Angular app with server-side rendering, what does `pendingUntilEvent()` from `@angular/core/rxjs-interop` do, and when would you add it?
answer
- application stability
- PendingTasks under the hood
- first next, complete, error or unsubscribe
- HttpClient and navigations already tracked
- developer preview
basics
~10 sAngular's pendingUntilEvent() operator adds a PendingTasks task when the Observable is subscribed and removes it at the first next, complete, error or unsubscribe, keeping the app unstable so server rendering waits for that value.
solid answer
~40 sWithout zone.js, Angular cannot infer that arbitrary async work is still running, so server-side rendering serialises the page as soon as its **pending tasks** are gone. Angular already tracks its own work, such as in-flight `HttpClient` requests and router navigations. `pendingUntilEvent()` (developer preview since v20) extends that to any Observable: on subscribe it calls `PendingTasks.add()`, and it removes the task on the **first** `next`, `complete`, `error` or unsubscribe. Add it to a stream whose first value must be in the server HTML but which Angular does not track, for example a quote snapshot from a custom client or a timer-delayed load. It needs an injection context or an `Injector` argument, and only the first event counts, so a long-lived feed releases stability after its first value.
code
ts · 14 linesimport { Component, inject } from '@angular/core';
import { pendingUntilEvent, toSignal } from '@angular/core/rxjs-interop';
import { QuotesClient } from './quotes-client';
@Component({
selector: 'app-quote-snapshot',
template: `{{ snapshot()?.price ?? '...' }}`,
})
export class QuoteSnapshot {
// QuotesClient is not built on HttpClient, so Angular does not track it on its own.
readonly snapshot = toSignal(
inject(QuotesClient).snapshot$('AAPL').pipe(pendingUntilEvent()),
);
}go deeper
Know that server rendering waits for pending work before sending HTML, and that pendingUntilEvent marks an Observable as such work.
Explain PendingTasks, the first-event rule, and why HttpClient and router navigations need no extra marking.
Identify untracked async sources in zoneless SSR, add the operator only where the first value must be server-rendered, and avoid stalling on silent feeds.
Decide which data belongs in server HTML versus client hydration, and whether a developer-preview API is acceptable in the rendering path.
## Why stability matters without zone.js When Angular renders on the server, it waits until the application is **stable** before serialising the HTML, so data loaded during rendering appears in the page. In zone-based apps zone.js tracked pending macrotasks and microtasks. Since v21 new applications are **zoneless** by default, and Angular's zoneless guide says that SSR then relies on the **`PendingTasks`** service: serialisation waits for the first moment all pending tasks have been removed. Angular adds pending tasks for its own asynchronous work. The guide names examples including an ongoing **router navigation** and an incomplete **`HttpClient`** request. Work that Angular did not start, such as a third-party client, a custom transport or a timer, is invisible unless you register it. ## What pendingUntilEvent does `pendingUntilEvent(injector?)` is an RxJS operator from `@angular/core/rxjs-interop`, marked **developer preview since v20**: 1. It resolves `PendingTasks` from the given `Injector`, or from the current injection context when called without one. 2. When the resulting Observable is **subscribed**, it adds a pending task. 3. It removes that task on the **first** of: a `next` value, `complete`, `error`, or unsubscription. ```ts readonly snapshot = toSignal( this.quotesClient.snapshot$('AAPL').pipe(pendingUntilEvent()), ); ``` In this sketch, `quotesClient` stands for a client that is not built on `HttpClient`. The server waits for the first snapshot before serialising, so the price is in the HTML. ## Details that matter | Aspect | Behaviour | |---|---| | task added | on each subscription | | task removed | first `next`, `complete`, `error` or unsubscribe | | a long-lived stream | stability returns after its **first** value | | a stream that never emits or ends | the app stays unstable and rendering keeps waiting | | outside an injection context | pass an `Injector` | - **It must actually be subscribed during rendering.** A stream that nobody subscribes to on the server adds no task. - **Do not add it to `HttpClient` calls** just in case; they are tracked already. - **Be careful with endless or silent sources**: a WebSocket that never sends on the server would hold rendering back, so such feeds are usually not opened on the server at all. - For **Promise-based** work, `PendingTasks.run(async () => ...)` or `add()` with a cleanup in `finally` is the equivalent. ## When to add it 1. The value must be in the **server-rendered HTML** (price in a product page, title, SEO-relevant content). 2. The source is **not** an Angular-tracked API. 3. The source **emits or completes in bounded time**. If any of those is false, leave it out. ## What interviewers listen for - Zoneless SSR depends on **`PendingTasks`**, not zone.js, to know when to serialise. - `pendingUntilEvent` adds a task on subscribe and removes it at the **first event**. - Angular already tracks **`HttpClient`** and **navigations**. - It is **developer preview**, and endless silent sources can stall rendering.
- Why is pendingUntilEvent unnecessary on an HttpClient call?Angular's HttpClient already registers its in-flight requests as pending tasks, so server rendering waits for them. Adding the operator only duplicates the tracking. It is for work Angular did not start itself.
- What happens if a stream with pendingUntilEvent never emits, completes or errors on the server?Its pending task is never removed, so the application never becomes stable and serialisation keeps waiting. Only use the operator on sources that produce their first event in bounded time, and avoid opening endless feeds during server rendering.
saying these in an interview costs you the question
- pendingUntilEvent keeps the app unstable until the Observable completes
- Every HttpClient call needs pendingUntilEvent to appear in SSR output
- pendingUntilEvent works without the stream ever being subscribed
- pendingUntilEvent is a stable API with no preview label
- Zoneless SSR still uses zone.js to detect pending work