angular16ngneat

What is the difference between takeUntil(this.destroyed), takeUntilDestroyed and untilDestroyed(this)?


To prevent memory leaks from open subscriptions in Angular we should unsubscribe from open subscriptions on component destroy.
I found this type of construct in SO and other places:

export class Component implements OnInit, OnDestroy {
  public data;
  destroyed = new Subject()
 
  ngOnInit(): void {
    this.service.getData()
      .pipe(
        takeUntil(this.destroyed),
      )
      .subscribe(
        response => this.data = response
      )
  }
 
  ngOnDestroy(): void {
    this.destroyed.next();
    this.destroyed.complete();
  }
}

And in Angular 16 there's a shorter way of doing the same thing with takeUntilDestroyed:

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 
export class Component implements OnInit{
  public data;
 
  constructor(private service: DataService) {
    this.service.getData()
      .pipe(takeUntilDestroyed())
      .subscribe(response => this.data = response);
  }
}

However, in our application we do the following using ngneat/until-destroy:

import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';

@UntilDestroy()
@Component({})
export class Component implements OnInit {
  public data;
 
  ngOnInit(): void {
    this.service.getData()
      .pipe(
        untilDestroyed(this)
      )
      .subscribe(response => this.data = response);
  }
}

I wander if there's still an advantage of using ngneat in Angular 16 or if it could be safely replaced by the new takeUntilDestroy operator. Are there any differences?

What did you try and what were you expecting?

I try to replace untilDestroyed(this) from ngneat with takeUntilDestroyed in Angular 16 and I expect no functional changes.


Solution

  • I run a few tests where I would log whether a subscription is open after the component is destroyed. I can confirm that both options (takeUntilDestroyed and ngneat/until-destroy) work exactly in the same way in Angular 16.
    One thing to keep in mind is that if takeUntilDestroyed is used outside a constructor it needs to have a destroy reference:

    import { Component, DestroyRef, inject } from '@angular/core';
    import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
     
    export class Component implements OnInit{
      public data;
      private destroyRef = inject(DestroyRef);
    
      constructor(private service: DataService) {}
    
      public ngOnInit(): void {
        this.service.getData()
          .pipe(takeUntilDestroyed(this.destroyRef))
          .subscribe(response => this.data = response);
      }
    }
    

    I'm not aware of any edge cases where a different behavior would arise. And I think in Angular 16 there's almost no benefit in using ngneat/until-destroy instead of takeUntilDestroyed.