Incompatibility Issues with TypeScript Function Overloading

In the process of setting up an NgRx store, I came across a pattern that I found myself using frequently:

 concatMap(action => of(action).pipe(
      withLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds)))
    )),

(found at the bottom of )

To avoid clutter in my code, I wanted to create a utility operator to streamline this pattern.

After some thought, I devised an implementation that should do the job:

export function concatMapLatestFrom<A extends Action>(
...xs: Array<ObservableInput<never>>
): OperatorFunction<A, unknown> {
return function (source: Observable<A>): Observable<unknown> {
    return source.pipe(
        concatMap((action) => of(action).pipe(withLatestFrom(...xs))),
    );
};
}

I also added some properly typed overloads:

export function concatMapLatestFrom<X1, A extends Action>(
source1: ObservableInput<X1>,
): { (source1: X1): OperatorFunction<A, [A, X1]> };

export function concatMapLatestFrom<X1, X2, A extends Action>(
source1: ObservableInput<X1>,
source2: ObservableInput<X2>,
): { (source1: X1, source2: X2): OperatorFunction<A, [A, X1, X2]> };

export function concatMapLatestFrom<X1, X2, X3, A extends Action>(
source1: ObservableInput<X1>,
source2: ObservableInput<X2>,
source3: ObservableInput<X3>,
): {
(source1: X1, source2: X2, source3: X3): OperatorFunction<
    A,
    [A, X1, X2, X3]
>;
};

However, I encountered issues where the compiler deemed the overload signature incompatible with the implementation. Even though I commented out one, the same issue arose with the next one in line.

The reason behind this discrepancy eludes me, and unfortunately, the compiler does not provide detailed explanations for these inconsistencies.

Answer №1

In my opinion, the culprits are the usage of never and unknown types in your code.

For example:

of(1)
  .pipe(
    concatMapLatestFrom(of('john'), of(true))
  ).subscribe(observer)

If I understand correctly, the observer is expected to receive a [number, string, boolean] tuple.

It seems that the function concatMapLatestFrom does not clearly define this:

function concatMapLatestFrom (): OperatorFunction<A, unknown> {}

This means we only have information about the source's type but need to properly infer the other types based on the ObservableInputs provided to concatMapLatestFrom.

Given this, here is an alternative approach:

export function concatMapLatestFrom<
  A extends Action,
  U extends Array<ObservableInput<any>>,
  R = Unshift<ObservedValueTupleFromArray<U>, A>
> (
  ...xs: U
): OperatorFunction<A, R> {
  return function (source: Observable<A>): Observable<R> {
    return source.pipe(
      concatMap((action) => of(action).pipe(withLatestFrom(...xs))),
    );
  };
}

The necessary types can be exported from 'rxjs':


// Mapped tuples: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html#mapped-types-on-tuples-and-arrays
// E.g [Observable<number>, Observable<string>] --> [number, string]
export type ObservedValueTupleFromArray<X> =
  X extends Array<ObservableInput<any>>
  ? { [K in keyof X]: ObservedValueOf<X[K]> }
  : never;

// Simply extract the value
export type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;

// Unshift<[B, C], A> --> [A, B, C]
export type Unshift<X extends any[], Y> =
  ((arg: Y, ...rest: X) => any) extends ((...args: infer U) => any)
  ? U
  : never;

Source

Answer №2

When dealing with overloaded functions, the type information is derived from the different overloads and the implementation must be capable of handling all possible parameters. Omitting the type arguments and return types can potentially resolve any issues that arise.

export function mergeMapLatestItems(
  ...items: Array<ObservableInput<never>>
) {
  return function(source: Observable<any>): Observable<unknown> {
    return source.pipe(
      mergeMap(item => of(item).pipe(withLatestFrom(...items)))
    );
  };
}

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

What is the function of the OmitThisParameter in TypeScript when referencing ES5 definitions?

I came across this specific type in the ES5 definitions for TypeScript and was intrigued by its purpose as the description provided seemed quite vague. /** * Removes the 'this' parameter from a function type. */ type OmitThisParameter<T> ...

Solving the issue of loading Ember Initializers during the transition to TypeScript

While following the ember quick start tutorial, I attempted to switch from Javascript to Typescript. Converting the .js files to .ts files resulted in an error with the ember-load-initializers import. The application is unable to run until this issue is re ...

Filtering Deno tests by filename: A step-by-step guide

How can I selectively run Deno tests based on their filenames? Consider the following test files: number_1_test.ts number_2_test.ts string_test.ts If I want to only run tests with filenames starting with number*, I am unable to use either of these comma ...

Guide to building a nested React component

My custom dropdown component requires 2 props: trigger (to activate the dropdown) list (content to display in the dropdown) Below is the implementation of my component: import { useLayer } from "react-laag"; import { ReactElement, useState } fr ...

Submit a Post request with a file and JSON information included

I'm encountering an issue while trying to pass files with a JSON object. It seems like there might be an error in the controller where the 'consumes' and 'produces' declarations are possibly incorrect. I need assistance on how to a ...

Unable to connect to Alpine store from an external source due to a typescript error

Here is how I have configured my Alpine store: Alpine.store( 'state', ({ qr: '' })) Now, I am attempting to update it from an external source as follows: Alpine.store( 'state' ).qr = 'test' However, I am encounte ...

Utilizing *ngIf for Showing Elements Once Data is Completely Loaded

While working on my Angular 2 app, I encountered an issue with the pagination UI loading before the data arrives. This causes a visual glitch where the pagination components initially appear at the top of the page and then shift to the bottom once the data ...

Is there a way to navigate directly to the code in a TypeScript type definitions index.d.ts file within Visual Studio Code?

When I command-click on the express() function, it takes me to its definition: const app = express(); In vscode, it leads me to this line in an index.d.ts file: declare function e(): core.Express; However, when I try to jump to the definition of e(), i ...

The perplexing actions of Map<string, string[]> = new Map() have left many scratching their heads

I encountered an issue while trying to add a value to a map in my Angular project. The map is initially set up using the following code: filters: Map<string, string[]> = new Map(); However, when I attempt to add a value to this map, it starts displa ...

Concealing tab bars on Ionic 2 secondary pages

In my Ionic Bootstrap configuration, I have the following setup: { mode: 'md', tabsHideOnSubPages: true } However, despite having this setting in place, the tabs still appear on some sub-pages. It seems to be happening randomly. Is there ...

Unfortunately, an exception was encountered: ES Module must be loaded using the import statement

Currently, I am addressing some vulnerability concerns within my Angular development environment. These vulnerabilities are found within internal dependencies, so I have included resolutions in the package.json file. However, when I attempt to run 'ng ...

Exploring the Module System of TypeScript

I am working with a TypeScript module structured like this: let function test(){ //... } export default test; My goal is for tsc to compile it in the following way: let function test(){ //... } module.exports = test; However, upon compilation, ...

JavaScript enables logging on Android Emulator

Currently, I am working with an Ionic app that is connected to SalesForce Mobile SDK. Due to the lack of support for the SDK and certain plugins in Ionic Serve, I have resorted to running the app in Android Studio using an Emulator - specifically, the Andr ...

Experiencing issues with Errors when Targeting ES5 in Angular2 TypeScript?

In my development environment, the npm version is 3.10.10, and I want to create a new Angular2 project from scratch. When I try running npm install angular2 --save I encounter this error message: Error Image After referring to this answer which recomm ...

Converting Data Types in Typescript

So I'm working with Data retrieved from a C# Rest Server. One of the values in the array is of type Date. When I try to perform calculations on it, like this: let difference = date1.getTime() - date2.getTime(); I encounter the following error messag ...

Encountered an error while trying to install @material-ui/core through npm: Received an unexpected end of JSON input

npm install @material-ui/core npm ERR! Unexpected end of JSON input while parsing near '...X1F+dSMvv9bUwJSg+lOUX' npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\WR-022\AppData\Roaming\npm-cach ...

Typescript not flagging an error for an object being declared without a type

I am encountering an issue with my tsconfig.json file: { "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": "src", "isolatedModules": true, "jsx": "preserve", "esModuleInterop": true, "forc ...

Is there a universal method to transform the four array values into an array of objects using JavaScript?

Looking to insert data from four array values into an array of objects in JavaScript? // Necessary input columnHeaders=['deviceName','Expected','Actual','Lost'] machine=['machine 1','machine 2&apo ...

Struggling to retrieve posted data using Angular with asp.net

I have encountered an issue while sending a post request from Angular to my ASP.NET server. I am trying to access the values of my custom model class (SchoolModel) and I can see that all the values are correct inside Angular. However, when I attempt to ret ...

Combining Vue with Typescript and rollup for a powerful development stack

Currently, I am in the process of bundling a Vue component library using TypeScript and vue-property-decorator. The library consists of multiple Vue components and a plugin class imported from a separate file: import FormularioForm from '@/FormularioF ...