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$));
};
}
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);
}
You can provide
destroyRef
manually as a pipe parameterYou 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`.
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));
}
}
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
Top comments (0)