Skip to content

Arigatouz/luftborn-rxjs-takeuntil-destroy

Repository files navigation

luftborn-rxjs-takeuntil-destroy

An ESLint plugin that enforces the use of takeUntilDestroyed or other take operators in RxJS subscription pipes to prevent memory leaks in Angular applications.

Description

This ESLint rule helps prevent memory leaks in Angular applications by ensuring that all Observable subscriptions use either the takeUntilDestroyed operator from @angular/core/rxjs-interop or one of the RxJS take operators (takeUntil, takeWhile, take, takeLast). These operators ensure that subscriptions are properly terminated, preventing memory leaks.

Installation

npm install luftborn-rxjs-takeuntil-destroy --save-dev

Requirements

  • ESLint 6.0.0 or higher (compatible with ESLint 9+)
  • Node.js 14.0.0 or higher
  • Angular 16.0.0 or higher (for @angular/core/rxjs-interop)
  • RxJS 7.0.0 or higher

Usage

Add the plugin to your ESLint configuration:

ESLint Config (eslintrc.js / eslintrc.json)

{
  "plugins": ["luftborn-rxjs-takeuntil-destroy"],
  "rules": {
    "luftborn-rxjs-takeuntil-destroy/require-take-until-destroyed": "error"
  }
}

ESLint Flat Config (eslint.config.js)

const luftbornRxjsTakeuntilDestroy = require('luftborn-rxjs-takeuntil-destroy');

module.exports = [
  {
    plugins: {
      'luftborn-rxjs-takeuntil-destroy': luftbornRxjsTakeuntilDestroy
    },
    rules: {
      'luftborn-rxjs-takeuntil-destroy/require-take-until-destroyed': 'error'
    }
  }
];

Rule Details

The require-take-until-destroyed rule enforces that all Observable subscriptions use either the takeUntilDestroyed operator or one of the RxJS take operators (takeUntil, takeWhile, take, takeLast) to ensure subscriptions are properly terminated, preventing memory leaks.

This rule is auto-fixable. When you run ESLint with the --fix option, it will automatically add takeUntilDestroyed() to your subscription pipes where needed.

Examples

❌ Incorrect

// Missing takeUntilDestroyed operator
this.observable.subscribe(result => {
  console.log(result);
});

// Using pipe but missing takeUntilDestroyed
this.observable.pipe(
  map(data => data)
).subscribe(result => {
  console.log(result);
});

✅ Correct

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

// Using takeUntilDestroyed in pipe
this.observable.pipe(
  map(data => data),
  takeUntilDestroyed()
).subscribe(result => {
  console.log(result);
});

// Using renamed import
import { takeUntilDestroyed as tud } from '@angular/core/rxjs-interop';

this.observable.pipe(
  map(data => data),
  tud()
).subscribe(result => {
  console.log(result);
});

// Using a variable with 'destroy' in the name
const destroyRef = inject(DestroyRef);
const untilDestroyed = takeUntilDestroyed(destroyRef);

this.observable.pipe(
  map(data => data),
  untilDestroyed
).subscribe(result => {
  console.log(result);
});

// Using takeUntil from RxJS
import { takeUntil } from 'rxjs';

destroy$ = new Subject<void>();

this.observable.pipe(
  map(data => data),
  takeUntil(this.destroy$)
).subscribe(result => {
  console.log(result);
});

// Using takeWhile from RxJS
import { takeWhile } from 'rxjs';

alive = true;

this.observable.pipe(
  map(data => data),
  takeWhile(() => this.alive)
).subscribe(result => {
  console.log(result);
});

// Using take from RxJS
import { take } from 'rxjs';

this.observable.pipe(
  map(data => data),
  take(1)
).subscribe(result => {
  console.log(result);
});

// Using takeLast from RxJS
import { takeLast } from 'rxjs';

this.observable.pipe(
  map(data => data),
  takeLast(1)
).subscribe(result => {
  console.log(result);
});

Rule Options

This rule has no options.

Auto-fix Behavior

When auto-fix is applied:

  1. For subscriptions without a pipe call, it adds a pipe with takeUntilDestroyed() before the subscribe call.
  2. For subscriptions with a pipe call but missing a take operator, it adds takeUntilDestroyed() as the last operator in the pipe.

Note: The auto-fix assumes that takeUntilDestroyed is imported from @angular/core/rxjs-interop. If it's not imported, you'll need to add the import manually.

Implementation Details

The rule checks for:

  1. Observable subscriptions without a pipe call
  2. Observable subscriptions with a pipe call but missing any of the supported take operators
  3. Proper import and usage of the takeUntilDestroyed operator from @angular/core/rxjs-interop
  4. Proper import and usage of RxJS take operators (takeUntil, takeWhile, take, takeLast) from 'rxjs'

The rule recognizes:

  • Renamed imports for all supported operators
  • Variables that might contain any of the supported operators
  • Various patterns for subscription cleanup

License

MIT © Luftborn

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository https://github.com/Arigatouz/luftborn-rxjs-takeuntil-destroy
  2. Create your feature branch (git switch -c feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

ESLint plugin that enforces the use of takeUntilDestroyed operator in RxJS subscription pipes to prevent memory leaks in Angular applications

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors