An ESLint plugin that enforces the use of takeUntilDestroyed or other take operators in RxJS subscription pipes to prevent memory leaks in Angular applications.
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.
npm install luftborn-rxjs-takeuntil-destroy --save-dev- 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
Add the plugin to your ESLint configuration:
{
"plugins": ["luftborn-rxjs-takeuntil-destroy"],
"rules": {
"luftborn-rxjs-takeuntil-destroy/require-take-until-destroyed": "error"
}
}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'
}
}
];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.
// 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);
});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);
});This rule has no options.
When auto-fix is applied:
- For subscriptions without a pipe call, it adds a pipe with
takeUntilDestroyed()before the subscribe call. - 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.
The rule checks for:
- Observable subscriptions without a pipe call
- Observable subscriptions with a pipe call but missing any of the supported take operators
- Proper import and usage of the
takeUntilDestroyedoperator from@angular/core/rxjs-interop - 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
MIT © Luftborn
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository https://github.com/Arigatouz/luftborn-rxjs-takeuntil-destroy
- Create your feature branch (
git switch -c feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request