A guide to building a versatile higher-order function using TypeScript

I'm struggling with creating a function that can add functionality to another function in a generic way. Here's my current approach:

/**
 * Creates a function that first calls originalFunction, followed by newFunction.
 * The created function returns the value returned by the original function
 * returned by the original function
 * @param originalFunction Function to be invoked first
 * @param newFunction Function to be invoked second
 */
function callAfter<T extends Function>(originalFunction: T, newFunction: T): any {
   return function() {
        const result = originalFunction.apply(this, arguments);
        newFunction.apply(this, arguments);
        return result;
   };
}

However, I'm encountering an error when trying to compile the above code:

TS2322: Type '() => any' is not assignable to type 'T'

I'm wondering if there's a way to maintain type safety while achieving this functionality? Right now, I've settled for making it return any to get it "working."

In continuing my exploration, I came up with the following modification:

/**
 * Creates a function that first calls originalFunction, followed by newFunction. The resulting function 
 * returns the value from the original function
 * @param originalFunction Function to be called first
 * @param newFunction Funct
 */
function callAfter<R, T extends () => R>(originalFunction: T, newFunction: T): T {
    return <T> function () {
        const result: R = originalFunction.apply(this, arguments);
        newFunction.apply(this, arguments);
        return result;
    };
}

Unfortunately, this version doesn't work for functions that return void, which was my primary use case at the moment.

The goal of my code snippet is to utilize a generic higher order function to enhance functionality in TypeScript.

/**
 * Component can leverage this feature to automatically remove subscriptions
 * when the component is no longer rendered in the DOM
 */
class SubscriptionTracker {
    private subscriptions: Subscription[] = [];

    constructor(destroyable: OnDestroy) {
        destroyable.ngOnDestroy = callAfter(destroyable.ngOnDestroy, () => {
            this.subscriptions.forEach((subscription) => subscription.unsubscribe());
        });
    }

    subscribe<T>(observable: Observable<T>, observer: PartialObserver<T>): Subscription {
        const subscription = observable.subscribe(observer);
        this.subscriptions.push(subscription);
        return subscription;
    }

    unsubscribe(subscription: Subscription) {
        subscription.unsubscribe();
        const indexOfSubscription = this.subscriptions.indexOf(subscription);
        if (indexOfSubscription == -1) {
            throw new Error('Unsubscribing to untracked subscription');
        }
        this.subscriptions.splice(indexOfSubscription, 1);
        return subscription;
    }
}

Answer №1

Understanding the question requires a focus on improving constraints related to return types and arguments. Instead of having the functor accept two functions of type T, it is beneficial to define a specific function type for each.

function executeAfter<O>(func: (...any) => O, func2: (...any) => void): (...any) => O {
    return (...args) => {
        let result = func(...args);
        func2(...args);
        return result;
    }
}

Here are a few use cases:

function doubleValue(x) { return x + x; };

function shoutOut(x) {
    console.log(`${x}!!!`);
}

let doubledAndShouted = executeAfter(doubleValue, shoutOut);
let output = doubledAndShouted(4501);
console.log(`doubledAndShouted(4501) = ${output}`);

This method also accommodates functions that return void on either side, as well as functions with multiple arguments:

executeAfter(shoutOut, shoutOut)("Ni");

let leakyReLUWithSound = executeAfter(function leakyReLU(x, alpha) {
    if (x < 0) x *= alpha;
    return x;
}, shoutOut);

console.log(`leakyReLUWithSound(-20, 0.2) = ${leakyReLUWithSound(-20, 0.2)}`);

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

Angular2 - Error: The view has been destroyed and cannot be updated: detectChanges

My application keeps encountering this persistent error: extensions::uncaught_exception_handler:8 Error in event handler for runtime.onMessage: Attempt to use a destroyed view: detectChanges at ViewDestroyedException.BaseException [as constructor] (chrome ...

The validation for the email field in Bootstrap, specifically in Angular 5, is not functioning properly for the "Email is Required

As a newcomer to Angular, I am seeking assistance with validation in my Angular 5 application. Specifically, I need to validate user emails and only allow navigation to a new component when a valid email is entered upon clicking a button. While pattern va ...

Silencing the warning message from ngrx/router-store about the non-existent feature name 'router'

After adding "@ngrx/router-store" to my project, I noticed that it fills the app console in development mode and unit test results with a repeated message: The warning states that the "router" feature name does not exist in the state. To fix this, make ...

Utilizing AWS CDK to Define StackProps Input Variables

Recently, I have started using the AWS CDK and encountered a challenge. I want to allow end users to define custom input variables when using my AWS CDK without having to edit the entire code. While I have been able to work with standard types such as stri ...

The term 'App' is being referenced as a value when it is intended to be a type. Perhaps you meant 'typeof App'?

I am eager to master Typescript with React through hands-on experience, so I recently made the manual transition from JavaScript to TypeScript in my create-react-app. However, when working with my default testing file App.test.ts: import { render, screen ...

Initiating the ngOnInit lifecycle hook in child components within Angular

I am facing an issue with controlling the behavior of child components in my Angular application. The problem arises when switching between different labels, causing the ngOnInit lifecycle hook of the children components not to trigger. The main component ...

Sending JSON object data to an API endpoint using the POST method in an Angular application

Attempted to post data to an API, but received a 400 bad request error. After testing with Postman, it seems that the issue may lie within my service or TypeScript code. As a newcomer to Angular, I am seeking assistance as I have searched extensively witho ...

In Javascript, check if an item exists by comparing it to null

I am working with a dropdown list that can be used for various types of data. Some of the data includes an isActive flag (a BOOLEAN) while others do not. When the flag is absent, I would like to display the dropdown item in black. However, if the flag exis ...

Create a data structure with a single key interface that contains a key value pair

Imagine having an interface with just one key and value : interface X { Y : string } It would be great to define a key-value type like this: interface Z { "key" : Y, "value" : string } However, manually doing this can be tedious. What if we ...

The distinction between <Type>function and function name():Type in TypeScript is essential to understand the various ways

function retrieveCounter(): Counter { let counter = <Counter>function (start: number) { }; counter.interval = 123; return counter; } Upon inspection of line 2 in the code snippet above, I am left wondering why I am unable to use function ...

Angular/Typescript: develop a factory function that creates objects based on the parent class's properties

I'm currently working on implementing a factory method that can return classes dynamically, and here's my code snippet: getWidget<T extends WidgetBase>(componentName: string): Type<T> { switch (componentName) { default: ...

Instructions on how to post an array by its ID when the value changes in the form, correspond with the ID

Whenever I change the value in the radio button within a form popup, I want to trigger this action. Below is the corresponding HTML code: <ng-container cdkColumnDef="injected"> <mat-header-cell *cdkHeaderCellD ...

Function that sets object properties based on specified keys and verifies the value

Let's consider a scenario where we have an object structured like this: interface Test{ a: number; b: string; c: boolean; } const obj:Test = { a: 1, b: '1', c: true, } We aim to create a function that can modify the value ...

Generate several invoices with just a single click using TypeScript

I'm interested in efficiently printing multiple custom HTML invoices with just one click, similar to this example: https://i.sstatic.net/hAQgv.png Although I attempted to achieve this functionality using the following method, it appears to be incorr ...

Implementing routerLinkActive for the same link in multiple sections in Angular

I am facing an issue with the routerLinkActive class in my application. I have two sections, one for pinned tools and one for all tools. Both sections have the same routerLink defined. The problem is that when I click on a link in the pinned tools section, ...

Fill up the table using JSON information and dynamic columns

Below is a snippet of JSON data: { "languageKeys": [{ "id": 1, "project": null, "key": "GENERIC.WELCOME", "languageStrings": [{ "id": 1, "content": "Welcome", "language": { ...

The type 'angular' does not have a property of this kind

Having trouble importing a method into my Angular component. An error keeps popping up: Property 'alerta' does not exist on type 'typeof PasswordResetService'. any I've double-checked the code and everything seems to be in order! ...

Failed to retrieve values from array following the addition of a new element

Does anyone have a solution for this problem? I recently added an element to my array using the push function, but when I tried to access the element at position 3, it wasn't defined properly processInput(inputValue: any): void { this.numOfIma ...

Vue 3 Composable console error: Unable to access properties of undefined (specifically 'isError') due to TypeError

I recently developed a Vue 3 / TypeScript Composable for uploading images to Firebase storage. The code snippet below illustrates the structure of the ImageUpload interface: interface ImageUpload { uploadTask?: UploadTask; downloadURL?: string; progr ...

Having trouble with clearInterval in my Angular code

After all files have finished running, the array this.currentlyRunning is emptied and its length becomes zero. if(numberOfFiles === 0) { clearInterval(this.repeat); } I conducted a test using console.log and found that even though ...