DEV Community

George Knap
George Knap

Posted on

2

takeUntilDestroyed() - How I got disappointed

Hi Friends, George here.

It was a big surprise for me - takeUntilDestroyed - finally something natively supported by Angular coming to version 16.

When it arrived I was disapponted. Why?
Well let's say I was hoping for something more seamless to use.

Let's have a look at how this new pipe is implemented:

export function takeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T> {
  if (!destroyRef) {
    assertInInjectionContext(takeUntilDestroyed);
    destroyRef = inject(DestroyRef);
  }

  const destroyed$ = new Observable<void>(observer => {
    const unregisterFn = destroyRef!.onDestroy(observer.next.bind(observer));
    return unregisterFn;
  });

  return <T>(source: Observable<T>) => {
    return source.pipe(takeUntil(destroyed$));
  };
}
Enter fullscreen mode Exit fullscreen mode

What is that weird destroyRef parameter?

DestroyRef is an injectable token which can take a callback onDestroy and this callback will be executed right before it's scope is destroyed. Angular needs this reference object to know when the to clean up your sbscription.

And that's the little annoying thing about this pipe. It's not magic! It doesn't work by itself! It always needs to have this destroyRef object.

How does it get it? There are two ways you can see in the code:

 if (!destroyRef) {
    assertInInjectionContext(takeUntilDestroyed);
    destroyRef = inject(DestroyRef);
  }
Enter fullscreen mode Exit fullscreen mode
  1. You can provide destroyRef manually as a pipe parameter

  2. You can let Angular to inject it by itself

This second option seems easy, right? No, because you need to be in the injection context.

Wait a sec, don't we have injection context everywhere?

No.

Here's where injection context works:

inject() must be called from an injection context 
such as a constructor, a factory function, a field initializer, 
or a function used with `runInInjectionContext`. 
Enter fullscreen mode Exit fullscreen mode

Get it?
You want to subscribe to an observable in ngOnInit and use takeUntilDestroyed to handle unsubscribe? Tough luck! You're not in the injection context.

How do we do it then?

We do it by injecting destroyRef in the injection context and passing it as a parameter to the pipe.
Example:

class MyComponent {
  destroyRef = inject(DestroyRef);
  ngOnInit() {
    timer(0, 1000).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(n => console.log('timer', n));
  }
}
Enter fullscreen mode Exit fullscreen mode

And here we are. The amount of code to do it is now quite similar to well known method of declaring an unsubscribe subject by yourself.

As we see this is not magic solution to all world's problems. In fact we should still keep in mind to try not to subscribe manually.

I was really looking forward to a future to discard our typescript mixin that handles unsubscribe logic inside a base class that you have to extend if needed. I might share it with you in another post so stay tuned.

Enjoy coding

To learn more about DestroyRef provider I highly recommend this video from Dmytro on Decoded Fronted Youtube channel

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Instrument, monitor, fix: a hands-on debugging session

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️