Typescript: Dynamic return type determined by argument properties

I have a function that should return two different types based on its argument props.

interface IPaginateParams {
  perPage: number;
  currentPage: number;
  isFromStart?: boolean;
}

interface IWithPagination<Data, TParams extends IPaginateParams = IPaginateParams> {
  data: Data;
  pagination: IPagination<TParams>;
}

type IPagination<TParams> = TParams extends
  | { currentPage: 1 }
  | { isFromStart: true }
  | { isLengthAware: true }
  ? ILengthAwarePagination
  : IBasePagination;

interface IBasePagination {
  currentPage: number;
  perPage: number;
  from: number;
  to: number;
}

interface ILengthAwarePagination extends IBasePagination {
  total: number;
  lastPage: number;
}

function paginate<TData = any[], TParams extends IPaginateParams = IPaginateParams>(
  options: TParams
): IWithPagination<TData, TParams>;

The concept is that if you specify currentPage: 1 or isFromStart: true, it should include two additional types in the pagination object.

Interestingly, IWithPagination behaves as intended,

const data = {} as IWithPagination<any, {perPage: 2, currentPage: 1}>;

expectType<ILengthAwarePagination>(data.pagination);

However, when using the function, it always returns the IBasePagination.

const data = paginate({perPage: 2, currentPage: 1});

expectType<ILengthAwarePagination>(data.pagination) // fails

// or

const data = paginate({perPage: 2, currentPage: 2, isFromStart: true});

expectType<ILengthAwarePagination>(data.pagination) // fails

Playground

Answer №1

According to @OlegValter's explanation in the comments, when an object is passed to paginate, it is interpreted as a widened type. For example:

{perPage: 2, currentPage: 2, isFromStart: true} // interpreted as {perPage: number; currentPage: number; isFromStart: boolean}

As a result, the return type check always defaults to the IBasePagination type (the else clause).

All that is needed is to specify that the function arguments are readonly.

declare function paginate<TData = any[], TParams extends IPaginateParams = IPaginateParams>(
  options: Readonly<TParams>
  // ---------^ this is what made the input as a narrow type
): IWithPagination<TData, TParams>;

Check out a working example

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

Dynamically load components within a modal window

I am looking for a way to dynamically load a custom component inside a modal while keeping it as flexible as possible. Here is an example : -HTML CODE- <button id="floating_button" class="floating_button animation_floating_in" (click)="loadCustomComp ...

In Typescript, convert an object into a different type while maintaining its keys in the resulting type

Imagine you have a code snippet like this type ResourceDecorator = (input: UserResourceDefinition) => DecoratedResourceDefinition const decorate: ResourceDecorator = ... const resources = decorate({ Book1: { resourceName: 'my-book', ...

Having trouble building my Angular 2 app with Angular-cli beta 14, despite it working perfectly fine with systemjs beforeEach

I have been using VSCode on Windows 10 to work on an app developed in Angular 2 final version, which runs smoothly with systemjs. Recently, I upgraded to angular-cli beta 14 webpack version and followed the steps outlined in the upgrade document here. How ...

I'm having trouble figuring out how to access response headers with HttpClient in Angular 5. Can anyone

I recently developed an authentication service in Angular 5, where I utilize the HttpClient class to make a POST request to my backend server. The backend server then responds with a JWT bearer token. Here is a snippet of how my request looks: return thi ...

Importing TypeScript Modules from a Custom Path without Using Relative Paths

If we consider the following directory structure: - functions - functionOne - tsconfig.json - index.ts - package.json - node_modules - layers - layerOne - tsonfig.json - index.ts - index.js (compiled index.ts ...

Struggling to center a MatIcon within a MatButtonToggle component in an Angular project

I've been struggling to center the MatIcon in the MatButtonToggle, trying multiple methods without success. It may seem like a minor issue, but it's causing quite a bit of trouble for me. Can someone please guide me on how to make this adjustment ...

Instance of "this" is undefined in Typescript class

After stumbling upon this code online, I decided to try implementing it in TypeScript. However, when running the code, I encountered an error: Uncaught TypeError: Cannot set property 'toggle' of null @Injectable() export class HomeUtils { p ...

Submitting a form using an anchor tag in Angular 8: A step-by-step guide

I have a question about how to submit form data using hidden input fields when a user clicks on an <a> tag. <form action="/submit/form/link"> <input type="hidden" [attr.value]="orderNumber.id" /> <input type="hidden" [attr.value]= ...

execute a rigorous compilation during the ng build angular process

I am currently working on a project using angular-cli and I have configured my package.json with the following scripts: "scripts": { "ng": "ng", "build": "ng build --base-href /test/", "prod": "ng build --prod --base-href /test/" } According to the ...

Creating a higher order component (HOC) that utilizes both withRouter and connect functions in React

I am currently working with the following stack: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="87f5e2e6e4f3c7b6b1a9b6b4a9b6">[email protected]</a> <a href="/cdn-cgi/l/email-protection" class="__cf_email__" dat ...

Utilizing HttpClient in a static method service with Angular

One of my services contains a static method that I use for some initialization treatment. Within this method, I need to retrieve data from a web service @Injectable() export class FeaturesInitializationService { static allowedFeaturesModul ...

What is the best approach to managing a 204 status in Typescript in conjunction with the Fetch API

Struggling to handle a 204 status response in my post request using fetch and typescript. I've attempted to return a promise with a null value, but it's not working as expected. postRequest = async <T>(url: string, body: any): Promise ...

A Vue object with dynamic reactivity that holds an array of objects

I've experimented with various approaches, but so far I've only managed to get this code working: // This works <script setup lang="ts"> import { reactive } from 'vue' import { IPixabayItem } from '../interfaces/IPi ...

Anonymous function's return type

Looking for advice on an anonymous function I've written: static oneOf(options: any[], cb?: Function) ValidatorFn { .... } I'm a TypeScript beginner and unsure how to specify that the 'cb' must return a boolean. Can this be done, an ...

"Experiencing issues retrieving user-modified values in NativeScript's TimePicker component

I'm having trouble retrieving the user-modified value from a TimePicker. Instead of getting the modified value, I keep receiving the original value that was initially set in the control. Below is my component template: <TabView [(ngModel)]="tabSe ...

The blank screen mystery: ionic and Google maps integration not playing nice

I've been struggling to integrate Google Maps into my web application. Unfortunately, all I see is a blank screen with no errors. Here's the current code snippet that I have. It seems like there might be an issue with the mapElement variable, but ...

Incorporate a personalized add-button into the material-table interface

My current setup includes a basic material-table structured like this: <MaterialTable options={myOptions} title="MyTitle" columns={state.columns} data={state.data} tableRef={tableRef} // Not functioning properly editabl ...

Troubleshooting: Angular input binding issue with updating

I am currently facing a challenge with connecting a list to an input object in Angular. I was expecting the updated values to reflect in the child component every time I make changes to the list, but strangely, the initial values remain unchanged on the sc ...

Is it possible to refresh the webpage in Angular when the tab is clicked?

Can someone help me find a solution to reload an Angular app's page when the user selects the browser tab? I've been exploring using window.location.reload() for this purpose, but I need guidance on triggering it specifically when the tab is sel ...

I am looking to transfer information from Angular 4 to Java servlet (cross-domain)

Having trouble sending data from Angular 4 to a Java servlet due to access control restrictions. Need to figure out how to properly insert data into the database using the Java servlet. Here is my code snippet: import { Injectable } from '@angular/ ...