Skip to main content

How to use RxVirtualView with hydration (SSR)

Goal. Use RxVirtualView in a server-side-rendered / hydrated app without a flash of placeholders. When the server sends full HTML, an IntersectionObserver that runs immediately on the client can replace freshly hydrated content with placeholders, causing a flash and destroying components that were just hydrated. Keep the virtual view disabled until hydration completes, then optionally enable it so that virtual behavior applies only after the app is interactive.

Step 1: Disable virtual behavior until hydrated (enabled)

Set the global config option enabled to a boolean or a Signal<boolean>. When enabled is false:

  • The directive renders content synchronously (no IntersectionObserver).
  • No placeholders are shown; everything behaves like normal, non-virtual content.

On the server and during hydration the user sees the full content. Once enabled turns true (e.g. hydration is done), the directive can start observing visibility.

Pass a signal that flips to true after hydration, for example the HydrationTracker from @rx-angular/cdk/ssr and its isFullyHydrated signal:

// app.config.ts
import { ApplicationConfig, inject } from '@angular/core';
import { provideVirtualViewConfig } from '@rx-angular/template/virtual-view';
import { HydrationTracker } from '@rx-angular/cdk/ssr';

export const appConfig: ApplicationConfig = {
providers: [
provideVirtualViewConfig(() => {
const hydrationTracker = inject(HydrationTracker);
return {
enabled: hydrationTracker.isFullyHydrated,
};
}),
],
};

Until isFullyHydrated is true, the virtual view stays disabled and shows content everywhere. After hydration, enabled becomes true and the directive can start virtualizing.

Step 2: Control behavior after hydration (enableAfterHydration)

When the directive starts disabled and later becomes enabled, decide whether it should start observing visibility and swap visible content for placeholders as elements scroll out of view. That is what enableAfterHydration controls (provider-level config only).

ValueBehavior
true (default)After enabled turns true, the directive registers the IntersectionObserver. Elements that scroll out of view show placeholders; virtual behavior is fully active.
falseAfter enabled turns true, the directive does not register the observer. Hydrated content stays as-is and is never replaced by placeholders. Use this to avoid destroying components that were just hydrated.

Keep hydrated content, no virtualizing afterwards:

provideVirtualViewConfig(() => {
const hydrationTracker = inject(HydrationTracker);
return {
enabled: hydrationTracker.isFullyHydrated,
enableAfterHydration: false, // hydrated nodes stay content; no placeholders later
};
});

Full virtual behavior after hydration (default):

provideVirtualViewConfig(() => {
const hydrationTracker = inject(HydrationTracker);
return {
enabled: hydrationTracker.isFullyHydrated,
enableAfterHydration: true, // after hydration, virtualize as usual (default)
};
});

When to choose which

Use enableAfterHydration: false when:

  • You want the first paint to match the server (no placeholders) and you are okay not virtualizing that page after hydration (e.g. long, mostly-static landing pages).
  • You want to avoid any risk of destroying freshly hydrated components or causing layout shifts right after hydration.

Keep enableAfterHydration: true (default) when:

  • You want full virtual behavior after hydration: once the app is interactive, elements that leave the viewport should show placeholders again to save DOM and work.

Result

ConfigEffect
enabled: false or a signal that becomes true after hydrationTurns off virtual behavior on the server and during hydration; turns it on only when appropriate (e.g. after HydrationTracker.isFullyHydrated).
enableAfterHydration: trueOnce enabled becomes true, start observing and show placeholders when elements leave the viewport.
enableAfterHydration: falseOnce enabled becomes true, do not start observing; keep hydrated content and never replace it with placeholders.

On the server and during hydration the user sees full content; after hydration the page behaves according to enableAfterHydration.

See also