How DestroyRef made my life easier with Angular 16

How DestroyRef Made My Life Easier with Angular 16

No need to use ngOnDestroy lifecycle-hook for unsubscribing anymore

Photo by Kevin Ku on Unsplash

We all know the story behind the necessity of completing subscriptions when the component is being destroyed, otherwise, we will introduce memory leaks and our application/browser/client’s machine will become slower and slower, due to loads of garbage within the memory.

There’ve been plenty of techniques since Observable Pattern became popular enough to be shipped with the most recent version of Angular at a time (game-changing version 2, now it’s already 16! Can you believe it?):

  • Using Subscription instance — we had to declare a new Subscription instance, assign the actual one and call its unsubscribe method when the component had been destroyed. This approach had its own limitations as you would have to declare separate subscription attributes per assignment as presented in the example below:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subscription } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

subscription: Subscription = new Subscription();

ngOnInit(): void {
this.subscription = of([]).subscribe();
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
  • Using subscriptions array — in this scenario, we are able to leverage as many subscriptions as we want with single subscriptions attribute. (Not bad right?) But if you think of clean code and readability, this doesn’t seem to be an ideal option either, as our code is being already wrapped with the push array method, yet an indentation has to be made. With a simple example as below it doesn’t seem to be an issue, but just imagine some more complex operations on multiple streams. Not to forget, we have to unsubscribe from each (sub) when the component is being destroyed like so:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subscription } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

subscriptions: Subscription[] = [];

ngOnInit(): void {
this.subscriptions.push(of([]).subscribe());
}

ngOnDestroy(): void {
this.subscriptions
.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
  • Using takeUntil rxjs operator combined with Subject — instead of subscription instance. We now use a new subject instance. I’ve seen multiple different flavors of type declaration used within. I think the most accurate is void as we do not really need an actual value, just an emission itself. Next, we need to remember to hook up takeUntil operator into the pipe. And make sure it will be the last one, cause if you’re using another operator returning higher-order observable like switchMap, it might not complete your stream, switchMap will be still operating. With this technique, we still keep the ngOnDestroy lifecycle-hook, but this time, calling the next method on our subject.

There is no need to complete subject stream, as there is no actual subscription to it being made. Therefore, no need to call: this.destroy.complete() at all.

P.S. If you’re not sure if your subscription is completed, you can always use finalize rxjs operator, which will gets triggered when your subscription completes. Voila! We’re safe 🙂

import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subject, Subscription, takeUntil } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

private readonly destroy: Subject<void> = new Subject<void>();

ngOnInit(): void {
of([])
.pipe(takeUntil(this.destroy.asObservable()))
.subscribe();
}

ngOnDestroy(): void {
this.destroy.next();
}
}

I don’t have to remind, that those activities had to be replicated within every component, where subscriptions are being used! Seems like additional activity to perform and extra code to add and remember about.

As developers, we want to constantly improve and make our life as easy as possible. I’ve already seen implementations where base component is being introduced just to keep the subscription implementation within one place only. To be honest, personally I’m not a huge fun of mentioned approach as we are already introducing additional layer of abstraction and inheritance which will be shipped to every component, complicating unit testing and bringing additional bits for super constructor calls.

This also opens pandora box, for less experienced developers to add some more to this base component (There is always something to add there!).

I’ve also seen some implementations using @Decorators and custom rxjs operators, which is a definitely leaner approach, but then you were forced to maintain it on your own and figure out the approach to reuse it within multiple projects (there are couple of techniques to approach it as well, but it’s a story for another article).

There’s already library in place from Netanel Basal so called @ngneat/until-destroy which brought a nice experience to our code base. I’ve already used it in couple of projects as well. One remark on my end, you really need to understand how it works to benefit from it, otherwise memory leaks, here we go!

Finally, Angular 16 was released with an amazing DestroyRef provider! Which can be easily used, we do not need much, just an injection token with DestroyRef and takeUntilDestroyed operator straight from @angular/core/rxjs-interop package. You have to see it in action! No need to use ngOnDestroy lifecycle-hook for unsubscribing anymore.

import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { of } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit {

private readonly destroy: DestroyRef = inject(DestroyRef);

ngOnInit(): void {
of([])
.pipe(takeUntilDestroyed(this.destroy))
.subscribe();
}
}

💡 You can even go further and create your own Rxjs operator, which will be super simple and easy to maintain! Use DestroyRef to create an operator called untilDestroyed, for example. You can then use an open-source toolchain like Bit to generate its documentation automatically, then publish, version, and reuse it across all of your projects with a simple bit import your.username/untilDestroyed command.

Find out more here:

Overview | Bit

Thank you Angular team! This will significantly reduce my code base!

Build Angular Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Introduction to Angular | Bit

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Learn more:


How DestroyRef made my life easier with Angular 16 was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Kamil Konopka

How DestroyRef Made My Life Easier with Angular 16

No need to use ngOnDestroy lifecycle-hook for unsubscribing anymore

Photo by Kevin Ku on Unsplash

We all know the story behind the necessity of completing subscriptions when the component is being destroyed, otherwise, we will introduce memory leaks and our application/browser/client’s machine will become slower and slower, due to loads of garbage within the memory.

There’ve been plenty of techniques since Observable Pattern became popular enough to be shipped with the most recent version of Angular at a time (game-changing version 2, now it’s already 16! Can you believe it?):

  • Using Subscription instance — we had to declare a new Subscription instance, assign the actual one and call its unsubscribe method when the component had been destroyed. This approach had its own limitations as you would have to declare separate subscription attributes per assignment as presented in the example below:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subscription } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

subscription: Subscription = new Subscription();

ngOnInit(): void {
this.subscription = of([]).subscribe();
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
  • Using subscriptions array — in this scenario, we are able to leverage as many subscriptions as we want with single subscriptions attribute. (Not bad right?) But if you think of clean code and readability, this doesn’t seem to be an ideal option either, as our code is being already wrapped with the push array method, yet an indentation has to be made. With a simple example as below it doesn’t seem to be an issue, but just imagine some more complex operations on multiple streams. Not to forget, we have to unsubscribe from each (sub) when the component is being destroyed like so:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subscription } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

subscriptions: Subscription[] = [];

ngOnInit(): void {
this.subscriptions.push(of([]).subscribe());
}

ngOnDestroy(): void {
this.subscriptions
.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
  • Using takeUntil rxjs operator combined with Subject — instead of subscription instance. We now use a new subject instance. I’ve seen multiple different flavors of type declaration used within. I think the most accurate is void as we do not really need an actual value, just an emission itself. Next, we need to remember to hook up takeUntil operator into the pipe. And make sure it will be the last one, cause if you’re using another operator returning higher-order observable like switchMap, it might not complete your stream, switchMap will be still operating. With this technique, we still keep the ngOnDestroy lifecycle-hook, but this time, calling the next method on our subject.
There is no need to complete subject stream, as there is no actual subscription to it being made. Therefore, no need to call: this.destroy.complete() at all.
P.S. If you’re not sure if your subscription is completed, you can always use finalize rxjs operator, which will gets triggered when your subscription completes. Voila! We’re safe :-)
import { Component, OnDestroy, OnInit } from '@angular/core';
import { of, Subject, Subscription, takeUntil } from 'rxjs';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit, OnDestroy {

private readonly destroy: Subject<void> = new Subject<void>();

ngOnInit(): void {
of([])
.pipe(takeUntil(this.destroy.asObservable()))
.subscribe();
}

ngOnDestroy(): void {
this.destroy.next();
}
}

I don’t have to remind, that those activities had to be replicated within every component, where subscriptions are being used! Seems like additional activity to perform and extra code to add and remember about.

As developers, we want to constantly improve and make our life as easy as possible. I’ve already seen implementations where base component is being introduced just to keep the subscription implementation within one place only. To be honest, personally I’m not a huge fun of mentioned approach as we are already introducing additional layer of abstraction and inheritance which will be shipped to every component, complicating unit testing and bringing additional bits for super constructor calls.

This also opens pandora box, for less experienced developers to add some more to this base component (There is always something to add there!).

I’ve also seen some implementations using @Decorators and custom rxjs operators, which is a definitely leaner approach, but then you were forced to maintain it on your own and figure out the approach to reuse it within multiple projects (there are couple of techniques to approach it as well, but it’s a story for another article).

There’s already library in place from Netanel Basal so called @ngneat/until-destroy which brought a nice experience to our code base. I’ve already used it in couple of projects as well. One remark on my end, you really need to understand how it works to benefit from it, otherwise memory leaks, here we go!

Finally, Angular 16 was released with an amazing DestroyRef provider! Which can be easily used, we do not need much, just an injection token with DestroyRef and takeUntilDestroyed operator straight from @angular/core/rxjs-interop package. You have to see it in action! No need to use ngOnDestroy lifecycle-hook for unsubscribing anymore.

import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { of } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit {

private readonly destroy: DestroyRef = inject(DestroyRef);

ngOnInit(): void {
of([])
.pipe(takeUntilDestroyed(this.destroy))
.subscribe();
}
}

💡 You can even go further and create your own Rxjs operator, which will be super simple and easy to maintain! Use DestroyRef to create an operator called untilDestroyed, for example. You can then use an open-source toolchain like Bit to generate its documentation automatically, then publish, version, and reuse it across all of your projects with a simple bit import your.username/untilDestroyed command.

Find out more here:

Overview | Bit

Thank you Angular team! This will significantly reduce my code base!

Build Angular Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Introduction to Angular | Bit

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Learn more:


How DestroyRef made my life easier with Angular 16 was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Kamil Konopka


Print Share Comment Cite Upload Translate Updates
APA

Kamil Konopka | Sciencx (2023-05-22T11:10:32+00:00) How DestroyRef made my life easier with Angular 16. Retrieved from https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/

MLA
" » How DestroyRef made my life easier with Angular 16." Kamil Konopka | Sciencx - Monday May 22, 2023, https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/
HARVARD
Kamil Konopka | Sciencx Monday May 22, 2023 » How DestroyRef made my life easier with Angular 16., viewed ,<https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/>
VANCOUVER
Kamil Konopka | Sciencx - » How DestroyRef made my life easier with Angular 16. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/
CHICAGO
" » How DestroyRef made my life easier with Angular 16." Kamil Konopka | Sciencx - Accessed . https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/
IEEE
" » How DestroyRef made my life easier with Angular 16." Kamil Konopka | Sciencx [Online]. Available: https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/. [Accessed: ]
rf:citation
» How DestroyRef made my life easier with Angular 16 | Kamil Konopka | Sciencx | https://www.scien.cx/2023/05/22/how-destroyref-made-my-life-easier-with-angular-16/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.