Custom render strategies
Legacy guidance — since Angular v21, change detection is zoneless by default and Zone.js is dropped from the default bundle. This page documents a legacy approach; Angular <21 / zoneful may still need it. For new work, prefer the modern approach. See Zoneless & how Zone.js affected change detection for the full picture.
Authoring a custom render strategy means writing your own change-detection render method and scheduling behaviour, a niche that mattered when Angular's change detection ran globally through Zone.js. Since Angular v21 change detection is zoneless by default and signals re-render only what changed, so hand-rolled strategies are rarely needed. For the built-in sets, prefer the native and concurrent strategies; reach for a custom strategy only for the audience named above. See Zoneless & how Zone.js affected change detection.
A custom strategy is a RxStrategyCredentials object registered through
provideRxRenderStrategies({ customStrategies }). It defines a render method
(work) and a scheduling behaviour (behavior). For why the strategy system
exists at all, and how vanilla change detection over-renders, see
Understanding change detection in Angular.
Import path
import { provideRxRenderStrategies } from '@rx-angular/cdk/render-strategies';
The types you implement
export type RxRenderWork = <T = unknown>(cdRef: ChangeDetectorRef, scope?: coalescingObj, notification?: RxNotification<T>) => void;
export type RxRenderBehavior = <T = unknown>(params: { work: () => any; scope?: coalescingObj; ngZone?: NgZone }) => (o: Observable<T>) => Observable<T>;
export interface RxStrategyCredentials<S = string> {
name: S;
work: RxRenderWork;
behavior: RxRenderBehavior;
}
export type RxCustomStrategyCredentials<T extends string> = Record<T, RxStrategyCredentials>;
export type RxNativeStrategyNames = 'native' | 'local' | 'noop';
export type RxConcurrentStrategyNames = 'immediate' | 'userBlocking' | 'normal' | 'low' | 'idle';
export type RxDefaultStrategyNames = RxNativeStrategyNames | RxConcurrentStrategyNames;
export type RxStrategyNames<T extends string = string> = RxDefaultStrategyNames | T;
export type RxStrategies<T extends string> = RxCustomStrategyCredentials<RxStrategyNames<T>>;
Two shapes are easy to get wrong:
behaviortakes a single params object, not positional arguments. Its parameter is{ work, scope?, ngZone? }; aRxRenderBehaviorreturns an operator that pipes the source through your scheduling ofwork().RxStrategyNames<T>is generic-defaulted asRxStrategyNames<T extends string = string>. The built-in native names are exactlynative | local | noop; there is noglobalnative strategy.
Configuration type
export interface RxRenderStrategiesConfig<T extends string> {
primaryStrategy?: RxStrategyNames<T>;
customStrategies?: RxCustomStrategyCredentials<T>;
patchZone?: boolean;
/** @deprecated dropped soon; not required with signal-based view & content queries */
parent?: boolean;
}
RxRenderStrategiesConfig also carries a parent? flag (deprecated; it is no
longer required with signal-based view and content queries).
Registering a custom strategy
import { provideRxRenderStrategies } from '@rx-angular/cdk/render-strategies';
import { asapScheduler } from 'rxjs';
import { delay, tap } from 'rxjs/operators';
export const appConfig: ApplicationConfig = {
providers: [
provideRxRenderStrategies({
primaryStrategy: 'asap',
customStrategies: {
asap: {
name: 'asap',
work: (cdRef) => cdRef.detectChanges(),
behavior:
({ work }) =>
(o$) =>
o$.pipe(
delay(0, asapScheduler),
tap(() => work()),
),
},
},
}),
],
};
See also
- Reference: Concurrent strategies
- Reference:
RxStrategyProvider - Concept: Understanding change detection in Angular
- Concept: Zoneless & how Zone.js affected change detection