Make sure to call super.onDestroy() in the child component before overriding it

I find myself with a collection of components that share similar lifecycle logic, so I decided to create a base component that implements the OnDestroy interface.

abstract class BaseComponent implements OnDestroy {
   subscriptions = new Array<Subscription>();
   get model() { return … }

   ngOnDestroy() {
      for (let s of subscriptions) s.unsubscribe();
   }
}

The issue arises when a developer writes a custom onDestroy method in a concrete Component that extends ComponentBase, as there is no clear indication that they need to call super.ngOnDestroy();

Is there a way in TypeScript to provide a warning for this? Or is there another design pattern besides component inheritance? perhaps a unit test could be written to verify ngOnDestroy on all components extending from BaseComponent?

EDIT I have come to realize that the BaseComponent with an array of subscriptions mentioned above is not a good practice and should be avoided. It would be better to use auto-unsubscribing observables.

Take a look at the takeUntil(destroy$) pattern:

class MyComponent implements OnInit, OnDestroy {    
    destroy$ = new Subject();    
  
    constructor(http: HttpService) { }

    ngOnInit() {
        http.get(...).pipe(
          takeUntil(this.destroy$)
        ).subscribe(...);  
    }
    

    ngOnDestroy() {
      destroy$.next();
   }
}

Answer №1

To ensure proper execution, it is recommended to establish a return type for ngOnDestroy that the child component must also adhere to.

class REQUIRED_SUPER {} //important to not export it, only we should be able to create it.

class Base implements OnDestroy {
    ngOnDestroy(): REQUIRED_SUPER {
        return new REQUIRED_SUPER;
    }
}

If the child component fails to return the specified type, it indicates that the method has not been invoked.

export class Child extends Base implements OnDestroy {
    ngOnDestroy(): REQUIRED_SUPER {
    }
}

This results in

TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.

To rectify this issue, the user must follow these guidelines:

ngOnDestroy(): REQUIRED_SUPER {
    return super.ngOnDestroy();
}

or

ngOnDestroy(): REQUIRED_SUPER {
    const superCalled = super.ngOnDestroy();
    //perform additional tasks
    return superCalled;
}

Answer №2

It might be the eleventh hour, but for those who are in need of it!!!

declare abstract class BaseClass implements OnDestroy {

ngOnDestroy(): void { }

constructor() {
    const refOnDestroy = this.ngOnDestroy;
    this.ngOnDestroy = () => {
        refOnDestroy();
       // implement unsubscriptions here
       // e.g. for (let s of subscriptions) s.unsubscribe();
    };
 }
}

For more information, click here

Answer №3

In most object-oriented programming languages, the feature you are searching for is not readily available. Once a method is overridden by a child class, there is no built-in way to ensure that the child class invokes the parent's implementation. In TypeScript, there is an ongoing discussion regarding this functionality in an open issue.

One alternative approach could involve marking the implementation of `ngOnDestroy` in the base class as `final`, and providing base classes with a hook-up method to enable them to delegate tear-down logic. For example:

abstract class BaseComponent implements OnDestroy {
   readonly subscriptions = new Array<Subscription>();
   get model() { return … }

   ngOnDestroy() {
      for (let s of subscriptions) s.unsubscribe();
      this.destroyHook();
   }

   // Depending on your requirements, you may consider having a default NOOP implementation
   // and allowing child classes to override it. This way, you can avoid scattering NOOP 
   // implementations throughout your codebase.
   abstract protected destroyHook(): void;
}


class ChildClass extends BaseComponent {
   protected destroyHook(){//NOOP}
}

Unfortunately, TypeScript does not currently support an equivalent of the `final` keyword at the moment, as discussed in this GitHub issue.

Another noteworthy aspect is that the challenge you are facing stems from how you plan to manage subscriptions on component instances. There are more effective ways to handle this, such as unsubscribing from source observables when the component is destroyed. You can achieve this using something like:

readonly observable$: Observable<string> = ....;
ngOnInit(){
   observable$.pipe(takeUntil(/*this instance is destroyed*/)).subscribe(...)
}

This can be easily accomplished with libraries like this one.

Answer №4

To prevent developers from including a subscribe in components and avoid the need to unsubscribe, consider leveraging reactive programming with rxjs operators and async pipes.

An alternative approach would be to implement a custom class decorator that evaluates all class members to identify instances of subscriptions.

Another option is to utilize a custom rxjs operator that automatically unsubscribes when a component is being destroyed.

While there are several choices available, I recommend utilizing the first method for cleaner code and compatibility with onPush change detection strategy.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

typescript tips for incorporating nested types in inheritance

I currently have a specific data structure. type Deposit { num1: number; num2: number; } type Nice { num: number; deposit: Deposit; } As of now, I am using the Nice type, but I wish to enhance it by adding more fields to its deposit. Ultima ...

The page does not appear to be updating after the onClick event when using the useState hook

Having difficulty re-rendering the page after updating state using the useState hook. Although the state value changes, the page does not refresh. export function changeLanguage(props: Props) { const [languageChange, setLanguageChange] = React.useState( ...

Is it impossible to use type as a generic in TypeScript?

Struggling with TypeScript in React and encountered an issue. I decided to use a generic to build an abstracted class related to Axios. However, I ran into an ESLint error when using any as the type parameter for my generic. ESLint: Unexpected any. Specif ...

Utilizing Angular 2's *ngFor to conditionally wrap elements can be compared to organizing a layout with three columns in a Bootstrap row, then starting a

Currently I am facing a challenge with using *ngFor and it has me puzzled. My goal is to incorporate UIkit, but the same concept would apply to Bootstrap as well. <div *ngFor="let device of devices" > <div class="uk-child-width-expand@s uk-te ...

Struggling to successfully pass a function as an argument to the setTimeout method within an array in node.js using TypeScript

Here is an example that successfully demonstrates a function being called using setTimeout: function displayMessage(msg: string){ console.log(msg); } setTimeout(displayMessage, 1000, ["Hi!"]; After one second, it will print out "Hi!" to the console. ...

The 'this' context setting function is not functioning as expected

Within my Vue component, I am currently working with the following code: import Vue from 'vue'; import { ElForm } from 'element-ui/types/form'; type Validator = ( this: typeof PasswordReset, rule: any, value: any, callback: ...

Is there a way to implement an extra placeholder attribute with Angular4?

Currently, in a project where I am utilizing Angular Material, there is a form integrated with an autocomplete component. The functionality works as expected but I am interested in implementing placeholder text once the user focuses on the input element. ...

Struggling with creating a generic TypeScript structure?

My goal is to manipulate data structured like this: const exampleState = { elements : { element1: { values: { value1: 10, value2: 10, }, elementDetails : { detail1 : { values: { value1: ...

Utilizing a variable string name for a method in Typescript Vue

My objective is to trigger a different function based on the value of a variable. However, when working in VS Code, I receive an error message that states: 'method' implicitly has a type of 'any' because 'type1Func' and &apos ...

Angular observable will only receive data once during initialization

Currently, I am progressing through Moshs' Angular course where we are building a simple shopping page. Despite the tutorial being a bit outdated, I managed to adapt to the changes in bootstrap and angular quite well until I reached the shopping cart ...

Having trouble invoking the "done" function in JQuery following a POST request

I am currently working on a Typescript project that utilizes JQuery, specifically for uploading a form with a file using the JQuery Form Plugin. However, after the upload process, there seems to be an issue when trying to call the "done" function from JQue ...

A TypeScript utility type that conditionally assigns props based on the values of other properties within the type

There is a common need to define a type object where a property key is only accepted under certain conditions. For instance, consider the scenario where a type Button object needs the following properties: type Button = { size: 'small' | &apo ...

I haven't encountered any type warnings in the places where I anticipated them

When I define an object like this: const x: { str: string, num: number } = { str: str, num: not_a_num }; I am surprised to find that even though 'not_a_num' is a string and not a number, the compiler does not throw an error. Instead, ...

Sharing packages within nested scopes

Using @organization-scope/package/sub-package in npm is what I want to achieve. Here is my package.json configuration:- { "name": "@once/ui", ... ... } If I try the following:- { "name": "@once/ui/select-box", ... ... } An error pops up st ...

What is the procedure for implementing a RoleGuard in Angular 6?

Is there a way to retrieve the user role from the token? I've managed to fetch the token using the decoding feature of angular2-jwt, but when I try to apply it for accessing the admin route, it returns a value of false. Upon checking with console.lo ...

incapable of altering the function of individual parts within ionic 3

I am currently working on creating a list of people for users to connect with. When a user clicks on the connect button, it should send a connection request to the chosen person. However, I am facing an issue where clicking on the connect button changes th ...

When deciding between StoreModule.forRoot and StoreModule.forFeature, consider the specific needs of your application

After researching, I discovered that the .forRoot function merges all reducers, allowing you to manipulate multiple reducers simultaneously when manipulating the state. On the other hand, the .forFeature function gives you the ability to manipulate each r ...

How to trigger a component programmatically in Angular 6

Whenever I hover over an <li> tag, I want to trigger a function that will execute a detailed component. findId(id:number){ console.log(id) } While this function is executing, it should send the id to the following component: export class ...

Utilize JavaScript libraries in a TypeScript project

Looking to integrate a payment system called iyzico into my project. The iyzico payment library can be found here. However, there are no types available for it. How can I utilize this library in my Node.js TypeScript Express backend project? I attempted t ...

The React Nested Loop Query: Maximizing Efficiency in Data

Learning React has been a challenge for me, especially when comparing it to XML/XPath. In this scenario, I have two arrays simplified with basic string properties... customerList: Customer[] export class Customer { id: string = ""; firstnam ...