Skip to main content

no-zone-critical-rxjs-schedulers

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

Use of RxJS schedulers that rely on Zone-patched timing (asyncScheduler, animationFrameScheduler, asapScheduler, queueScheduler), typically passed to observeOn/subscribeOn from rxjs. Emissions delivered through these schedulers run on patched timers and can trigger change detection.

Options

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

// eslintrc
{
"rules": {
"@rx-angular/no-zone-critical-rxjs-schedulers": "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-schedulers': 'error',
},
},
];

Incorrect

// ❌ Incorrect — asyncScheduler delivers on a Zone-patched timer
import { interval, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';

interval(10).pipe(observeOn(asyncScheduler)).subscribe(console.log);

Correct

// ✅ Correct — in a zoneless app (v21+) the scheduler is no longer Zone-patched,
// so the plain rxjs scheduler is fine as-is.
import { interval, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';

interval(10).pipe(observeOn(asyncScheduler)).subscribe(console.log);
Do not use rxjs-zone-less

Older guidance imported the scheduler from rxjs-zone-less. That package is unmaintained (RxJS 6/7 only); do not add it or present it as a current path. In a zoneless app you need no wrapper; in a residual Zone.js app, unpatch the underlying timer via getZoneUnPatchedApi from @rx-angular/cdk/internals/core.

Why

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