In Angular hydration, what does withEventReplay() do with clicks made before hydration finishes, and do you still need it in Angular 22?
answer
- visible before interactive
- capture, store, replay
- an inline contract script
- incremental hydration's side effect
basics
~10 swithEventReplay() captures native events such as clicks that happen before hydration and replays them once listeners are attached. In Angular 22 it is on by default, because default incremental hydration enables it.
solid answer
~40 sA server-rendered page is visible before hydration attaches listeners, so early clicks would be lost. Event replay, added to `provideClientHydration` as `withEventReplay()` in v18, fixes that: the served HTML carries a small inline event-dispatch script that listens on `document.body` for the event types the templates use, the event contract queues captured events, and after hydration Angular re-dispatches them to the real handlers. It covers native events like `click`, `mouseover` and `focusin`. In Angular 22 `provideClientHydration()` enables incremental hydration by default, and incremental hydration turns on event replay, so you only need `withEventReplay()` explicitly if you opted out with `withNoIncrementalHydration()`. It does not make the page interactive sooner; it only stops early input from disappearing.
code
ts · 11 linesimport { ApplicationConfig } from '@angular/core';
import {
provideClientHydration,
withEventReplay,
withNoIncrementalHydration,
} from '@angular/platform-browser';
// Opted out of incremental hydration, so event replay must be requested explicitly.
export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(withNoIncrementalHydration(), withEventReplay())],
};go deeper
Recall that a server-rendered page can be clicked before it is hydrated, and that event replay queues those clicks and replays them afterwards.
Explain capture, storage in the event contract and replay, and how v22's default incremental hydration turns event replay on.
Verify replay in production HTML, account for users clicking twice during a long gap, and know which opt-outs switch replay off.
Treat the gap between visible and interactive as a product metric and decide whether replay is enough or bootstrap time itself must shrink.
## The gap event replay closes A server-rendered Angular page is **visible before it is interactive**. The HTML arrives first; the bundle downloads, the app bootstraps and hydration attaches listeners only later. A user who clicks "Add to basket" in that gap would, without help, click an element that has no listener yet, and the click is lost. **Event replay** records those early interactions and re-dispatches them once hydration has attached the real listeners. ## How it works in Angular Event replay is built on the **event dispatch** library (the JSAction lineage) that ships inside Angular. It has three phases: 1. **Capture.** On the server, Angular notes which DOM event types the rendered templates listen to and adds a small inline script to the HTML (the Angular CLI inserts the dispatch script; the server adds a bootstrap call listing the event types). That script registers listeners on `document.body` for those event types as soon as the HTML is parsed. 2. **Store.** The **event contract** keeps the captured events in memory, with the element each one targeted. 3. **Replay.** When hydration has attached the component listeners, Angular re-invokes the queued events against them. It works with native browser events such as `click`, `mouseover` and `focusin`, the ones bound with `(event)` syntax in templates. ## Enabling it `withEventReplay()` has been available since **v18**, as a feature of `provideClientHydration`: ```ts import { ApplicationConfig } from '@angular/core'; import { provideClientHydration, withEventReplay } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [provideClientHydration(withEventReplay())], }; ``` In **Angular 22** the picture changed: | Configuration in v22 | Is event replay on? | | --- | --- | | `provideClientHydration()` | Yes, because incremental hydration is on by default and it enables event replay | | `provideClientHydration(withEventReplay())` | Yes; the feature is redundant but harmless | | `provideClientHydration(withNoIncrementalHydration())` | No | | `provideClientHydration(withNoIncrementalHydration(), withEventReplay())` | Yes | So in a current app you only write `withEventReplay()` explicitly if you have opted out of incremental hydration, or if you want the intent visible in the config. ## What it does not do - It does **not** make the page interactive earlier; handlers still run only after hydration. - It does **not** replay events on pages that are not hydrated, for example a `RenderMode.Client` route with nothing to hydrate. - It is **not** a substitute for a fast bootstrap. A long gap still means a delayed response to the click, only not a lost one. - It only covers event types that Angular's own listeners use; the server collects those types from the listeners it rendered, so handlers that other scripts attach later are outside the contract. ## A timeline of one early click 1. The HTML arrives and paints; the inline dispatch script runs and starts listening on `document.body`. 2. The user clicks "Add to basket"; the event contract records the event and its target. 3. The bundle finishes loading, Angular bootstraps and hydrates the product component, attaching its `(click)` listener. 4. Angular replays the recorded click to that listener; the item is added. Without replay, step 2 would record nothing and the click would vanish. ## Why interviewers ask it The question tests whether a candidate knows that server rendering creates a window in which the page looks usable but is not, and that Angular has a specific, opt-in mechanism for it whose default changed in v22. A strong answer names the three phases, the provider feature, and the v22 default through incremental hydration. ## Practical checks - Confirm the inline dispatch script is present in the served HTML; if a proxy or security policy strips inline scripts, capture never starts. - Test on a throttled network: click early, then watch the handler fire once hydration completes. - Keep handlers idempotent where a double action would matter, because users who see no immediate response tend to click again.
- Does event replay make a server-rendered page respond faster?No. Handlers still run only after hydration attaches them; replay only guarantees the early click is not lost. A long gap still feels slow, so bootstrap and hydration time matter just as much.
- What could stop event replay from capturing anything?Capture depends on the inline event-dispatch script in the served HTML. If something strips inline scripts or the script is blocked, no events are queued. Pages with nothing to hydrate, such as client-rendered routes, have nothing to replay either.
saying these in an interview costs you the question
- Event replay makes buttons work before the bundle has loaded
- In Angular 22 you must always add withEventReplay() yourself
- Clicks before hydration are replayed even without any hydration setup
- Event replay re-runs the whole page render after hydration
- withNoIncrementalHydration() has no effect on event replay