Skip to main content

How to run work once the app is fully hydrated

Goal. Defer app-level work (analytics, a heavy chart, a global "still hydrating" indicator) until your entire server-rendered page has finished hydrating, using HydrationTracker's app-wide isFullyHydrated signal.

Use this when you need one signal for the whole app. For hydrating an individual region on demand, prefer Angular's native incremental hydration (@defer (hydrate …), v19+) instead; this recipe is for the app-wide case that native per-block hydration does not cover.

Steps

1. Provide the tracker

Add provideHydrationTracker() to your application config. Omit the argument for the defaults (timeout 10000 ms, logging false), or pass a shorter timeout as a safety net:

import { ApplicationConfig } from '@angular/core';
import { provideHydrationTracker } from '@rx-angular/cdk/ssr';

export const appConfig: ApplicationConfig = {
providers: [
provideHydrationTracker({
timeout: 5000, // force completion after 5s even if some nodes remain
logging: true, // log completion / timeout while debugging
}),
],
};

2. Gate a template on the signal

Inject HydrationTracker and read isFullyHydrated() in the template. Render the heavy content only once it is true:

import { Component, inject } from '@angular/core';
import { HydrationTracker } from '@rx-angular/cdk/ssr';

@Component({
selector: 'app-shell',
template: `
@if (tracker.isFullyHydrated()) {
<app-heavy-chart />
} @else {
<p>Loading…</p>
}
`,
})
export class ShellComponent {
protected readonly tracker = inject(HydrationTracker);
}

3. Run one-off logic after hydration

For imperative work that should run exactly once when hydration completes, use the observable form and take a single emission:

import { Component, inject } from '@angular/core';
import { HydrationTracker } from '@rx-angular/cdk/ssr';
import { filter, take } from 'rxjs';

@Component({
/* … */
})
export class AnalyticsComponent {
private readonly tracker = inject(HydrationTracker);

ngOnInit() {
this.tracker.isFullyHydrated$.pipe(filter(Boolean), take(1)).subscribe(() => {
// run analytics or other post-hydration logic once
});
}
}

Result

The gated content appears, and your one-off logic runs, only after the whole page has hydrated (or after the timeout fires as a safety net). Confirm by enabling logging: true and watching for the completion log in the browser console; the signal flips from false to true at that moment. On a non-server-rendered browser load there is nothing to hydrate, so the signal is true immediately.

The tracker infers completion from Angular removing the internal ngh attribute as it hydrates each node. That marker is not a public contract, so treat the signal as a best-effort heuristic backed by the timeout; see the reference for the caveat.

See also