Skip to main content

no-zone-critical-rxjs-operators

Legacy guidance

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; Zone.js only may still need it. For new work, prefer the modern approach. See Zoneless & how Zone.js affected change detection for the full picture.

This rule only does anything when Zone.js is present. Since Angular v21, change detection is zoneless by default and Zone.js is dropped from the default bundle, so there is nothing to patch and this rule is a no-op. See Zoneless & how Zone.js affected change detection.

What it flags

RxJS operators that schedule on Zone-patched timers (debounceTime, throttleTime, delay, and similar time-based operators) imported from rxjs/operators. Their scheduled emissions run through a patched scheduler and can trigger change detection.

Options

This rule has no options (schema: []).

// eslintrc
{
"rules": {
"@rx-angular/no-zone-critical-rxjs-operators": "error",
},
}
// eslint.config.js (flat config)
import rxAngular from '@rx-angular/eslint-plugin';

export default [
{
plugins: { '@rx-angular': rxAngular },
rules: {
'@rx-angular/no-zone-critical-rxjs-operators': 'error',
},
},
];

Incorrect

// ❌ Incorrect — delay schedules on a Zone-patched timer
import { fromEvent, of } from 'rxjs';
import { mergeMap, delay, takeUntil } from 'rxjs/operators';

const mousedown$ = fromEvent(document, 'mousedown');
const mouseup$ = fromEvent(document, 'mouseup');

mousedown$.pipe(mergeMap((event) => of(event).pipe(delay(700), takeUntil(mouseup$)))).subscribe((event) => console.log('Long Press!', event));

Correct

// ✅ Correct — in a zoneless app (v21+) the operator's scheduler is no longer
// Zone-patched, so the plain rxjs/operators import is fine as-is.
import { fromEvent, of } from 'rxjs';
import { mergeMap, delay, takeUntil } from 'rxjs/operators';

const mousedown$ = fromEvent(document, 'mousedown');
const mouseup$ = fromEvent(document, 'mouseup');

mousedown$.pipe(mergeMap((event) => of(event).pipe(delay(700), takeUntil(mouseup$)))).subscribe((event) => console.log('Long Press!', event));
./rxjs-zoneless-operators is a user shim

Older examples imported the operator from a local ./rxjs-zoneless-operators file. That is a user-authored shim, not a shipped rx-angular path, so it is not an entry point you can install. In a zoneless app you need no shim; in a residual Zone.js app, unpatch the underlying timer via getZoneUnPatchedApi from @rx-angular/cdk/internals/core.

Why

RxJS operators backed by Zone-patched schedulers can trigger unnecessary change-detection runs. Why Zoneless & how Zone.js affected change detection.