Sunday 25 June 2023

DestroyRef Angular 16 - How DestroyRef Made Develpoer's Life Easier

We are all familiar with the importance of properly handling subscriptions when a component is being destroyed. Neglecting to do so can lead to memory leaks, causing our application, browser, or client machine to progressively slow down due to excessive memory usage and accumulated garbage.

When we subscribe to observables or other resources within a component, it's crucial to unsubscribe or complete those subscriptions when the component is no longer in use. If we fail to clean up these subscriptions during the destruction phase, they will continue to hold references to resources, preventing them from being garbage-collected. As a result, memory consumption grows over time, impacting the performance and responsiveness of our application.

Angular 16 has recently been released, offering a range of exciting new features. One noteworthy addition is the introduction of an injectable entity called DestroyRef. This entity can be injected into various Angular building blocks, including components and services.

DestroyRef untilDestroyed Angular 16

By utilizing DestroyRef, you can register a callback function within these building blocks. This callback function will be executed just before the associated scope is destroyed. It provides a convenient way to perform cleanup operations or additional logic before the scope is removed.

The DestroyRef feature simplifies the handling of pre-destruction actions across different Angular components and services. It ensures that necessary cleanup tasks are executed reliably, resulting in more robust and efficient Angular applications. With this new capability, developers can have better control over the destruction process of Angular scopes and enhance the overall performance of their applications.

All right! You might be wondering, "Isn't the ngOnDestroy hook already available in Angular? Why do we need DestroyRef?"

While ngOnDestroy is available, DestroyRef allows us to create reusable logic for performing cleanup tasks when a scope is destroyed, without the need for inheritance. This simplifies the implementation process and reduces complexity.

ngOnDestroy Exmaple:

typescript
import { Component, OnDestroy, OnInit } from '@angular/core'; import { of, Subscription } from 'rxjs'; @Component({ selector: 'app-thecoderevisited', templateUrl: './thecoderevisited.component.html', styleUrls: ['./thecoderevisited.component.css'] }) export class ThecoderevisitedComponent implements OnInit, OnDestroy { subscriptions: Subscription[] = []; ngOnInit(): void { this.subscriptions.push(of([]).subscribe()); } ngOnDestroy(): void { // Cleanup logic this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe()); } }

In the above code, we have an Angular component called ThecoderevisitedComponent that implements the OnInit and OnDestroy interfaces. Inside the component, there is an array called subscriptions of type Subscription[], which will store all the subscriptions we create.

In the ngOnInit lifecycle hook, we push a new subscription to the subscriptions array using the of([]).subscribe() method. This is just a placeholder observable (of([])) that doesn't emit any values. You can replace it with your actual observable.

In the ngOnDestroy lifecycle hook, we iterate over the subscriptions array and call unsubscribe() on each subscription. This ensures that all subscriptions are properly unsubscribed when the component is destroyed, preventing any memory leaks.

Limitation with ngDestory() :
It's unnecessary to emphasize that these activities had to be duplicated in every component where subscriptions are used. It appears as an additional task to perform and extra code to include and remember.

As developers, we continually strive to improve and simplify our lives. Some implementations I've come across introduce a base component solely to centralize subscription management in one place. Personally, I'm not particularly fond of this approach as it introduces an additional layer of abstraction and inheritance. This, in turn, adds complexity to unit testing and requires additional considerations for super constructor calls in each component.

As a developer, finding a balance between code simplicity and maintainability is crucial. While consolidating subscription logic in a base component can provide a centralized approach, it's important to weigh the trade-offs and consider alternative solutions that minimize complexity and improve testability. An alternative solution is DestroyRef.

DestroyRef :

Using DestroyRef is a straightforward process. From angular 16 onwards we can inject the DestroyRef provider as follows and register a destroy callback in the following manner:

typescript
@Component({ selector: 'foo', standalone: true, template: '', }) class ThecoderevisitedComponent { constructor() { inject(DestroyRef).onDestroy(() => { // Perform necessary cleanup tasks when the component is destroyed }); } }

As an example, we can create an untilDestroyed operator that relies on DestroyRef:

typescript
export function untilDestroyed() { const subject = new Subject(); inject(DestroyRef).onDestroy(() => { subject.next(true); subject.complete(); }); return <T>() => takeUntil<T>(subject.asObservable()); } @Directive({ selector: '[appFoo]', standalone: true, }) export class ThecoderevisitedDirective { private untilDestroyed = untilDestroyed(); ngOnInit() { interval(1000) .pipe(this.untilDestroyed()) .subscribe(console.log); } }

In this example, the untilDestroyed operator creates an observable that emits values until the associated scope is destroyed. It relies on the DestroyRef functionality to handle the destruction event. The ThecoderevisitedDirective uses this untilDestroyed operator within its ngOnInit lifecycle hook to subscribe to an interval observable and log the emitted values.

By leveraging DestroyRef and related utilities, we can simplify the process of performing cleanup tasks and ensure that our Angular components and directives are properly managed when destroyed.

Read more,

Thursday 22 June 2023

"Sequenced Collections": an upcoming feature of Java 21 to handle a sequence of elements

The Collection API in Java, found in the JDK, stands as one of the oldest and most active APIs. It encompasses various collection types, such as List, Set, and Map. However, a particular collection type that represents an ordered sequence of elements with standardized operations is absent from Java's collections framework.

For example, List and Deque both define an encounter order, but their shared supertype, "Collection," does not. Similarly, Set does not establish an encounter order, and neither do its subtypes like HashSet or SortedSet, although LinkedHashSet and SortedSet do exhibit such behavior. Consequently, support for encounter order is scattered throughout the type hierarchy, posing challenges when attempting to express certain useful concepts in APIs. Neither a collection nor a list can adequately describe a parameter or return result that relies on encounter order.

Sequenced Collections Java 21

To address this issue, new interfaces for sequenced collections, sequenced sets, and sequenced maps have been introduced, seamlessly integrated into the existing collection type hierarchy. Each of these interfaces includes default implementations for the newly declared methods. Sequenced collections represent collections with a defined encounter order, sequenced sets represent sets that maintain sequence and uniqueness, and sequenced maps represent maps with entries in a specific order. These objects possess unique properties and characteristics.

With the addition of these new interfaces, all sequenced types can process elements in both forward and backward directions using various common iteration mechanisms. These include enhanced for loops, explicit iterator() loops, forEach(), stream(), parallelStream(), and toArray(). This is made possible by the introduction of the reversed() method, which provides a reverse-ordered view of the original collection.

Overall, the Java Collections Framework has made significant advancements by incorporating new interfaces to represent collections with specified encounter order. These interfaces offer a uniform set of actions applicable across such collections. This enhances the framework's comprehensibility and effectiveness for developers, providing standardized and user-friendly support for encounter order.

Monday 19 June 2023

Angular 16: Understanding Required Input Properties, Example

Angular, the popular TypeScript-based web application framework, continues to evolve with new updates and features. In Angular 16, a notable addition is the concept of required input properties. These properties enable developers to enforce the presence of specific inputs within their components, providing better control and error handling. In this article, we will explore the importance of required input properties and how to leverage them effectively in Angular 16.

Angular 16: Understanding Required Input Properties

Why Required Input Properties Matter

When building Angular applications, components often rely on input properties to receive data from their parent components. These inputs play a crucial role in establishing communication and passing down information between components. However, there are scenarios where certain inputs are critical for a component's functionality and should not be left undefined or null.

Consider a scenario where a component expects a user object as an input. Without a required input property, developers would need to handle potential null or undefined values explicitly within the component. By making an input property required, developers can ensure that the component receives the necessary data, reducing the likelihood of runtime errors and enhancing code predictability.

Declaring Required Input Properties

To declare a required input property in Angular 16, the @Input decorator can be combined with the required keyword. This combination provides a straightforward and expressive way to indicate that a specific input property must be provided.

For example, let's assume we have a UserComponent that requires the user input property:

typescript
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-user', template: '<p>{{ user.name }}</p>' }) export class UserComponent { @Input() required user: User; }
In the above code snippet, the required keyword signifies that the user input property is mandatory for the UserComponent. If the parent component fails to provide the user input, the Angular compiler will raise an error during compilation, ensuring that the required input property is not overlooked.

Handling Missing Required Inputs

When a required input property is not provided, Angular 16 offers several strategies for handling such situations. The compiler error message will provide guidance on how to address the issue.

1. Provide a default value: If it makes sense for your component, you can set a default value for the required input property. This allows the component to work even if the input is not provided explicitly. To set a default value, you can initialize the property in the component's constructor or directly within the property declaration.

typescript
@Input() required user: User = { name: 'Default User' };

2. Conditional rendering: Another approach is to conditionally render the component or display an error message when the required input property is missing. This gives users a visual indication that the input is required and allows for graceful degradation of functionality.

html
<ng-container *ngIf="user"> <app-user [user]="user"></app-user> </ng-container> <ng-container *ngIf="!user"> <p>Please provide a user object.</p> </ng-container>

By implementing appropriate fallback strategies, developers can ensure that their components remain functional and user-friendly even when required input properties are not supplied.

Conclusion

Angular 16 introduces required input properties as a valuable addition to the framework's toolkit, empowering developers to enforce the presence of critical inputs within components. By marking input properties as required, developers can enhance code predictability, reduce runtime errors, and streamline error handling. Understanding and utilizing required input properties can greatly improve the robustness and reliability of Angular applications.

Also, read: 

Seven front-end development trends in 2023-2024

With the increasing prevalence of apps in the digital landscape , the role of front-end developers remains essential. While apps aim to ove...

Popular Posts