3

Let's say I have two functions like this (I currently have like 20 different that looks pretty much the same. Only difference is the route and the DTOs) I don't want to send both route and DTO in the function call as that would make the function calls more messy/complicated. So I'd rather only call it with either Asset | Personnel | Something else that is valid

 async getAllAssets() {
    return await this.get<AssetDto[]>('assets/assets')
  }

 async getAllPersonnels() {
    return await this.get<PersonnelDto[]>('personnel/personnels')
  }

And I want to make it more generic so I only need one function instead of two. How do I implement that? My own try is below. Maybe it will make it more clear what I actually want as well. I'm a total newbie with TypeScript and only been at it for one week. My "dream" implementation would also include enum so I could call the function with e.g. Entity.Asset or Entity.Personnel and then under the hood it then knows that it should use the the route and dto for either Asset or Personnel.

export type Asset = {
    route: '/assets/asset',
    dto: AssetDto[]
}

export type Personnel = {
    route: '/personnel/personnel',
    dto: PersonnelDto[]
}

export type Entity = Asset | Personnel

And here is the example of a more generic function:

 async getAll<T extends Entity>(entity: T) {
    return await this.get<typeof entity.Dto>(entity.route)
  }

But I don't know how to actually call the function with a type? Or is it even possible to do it like this?

  async howIWantAFunctionCallToBeLike() {
    await this.getAll(Entity.Asset))
  }
1
  • no, you can not call a function with just a type since types don't exist at runtime. You need to pass at least something to the function so that it knows at runtime which endpoint to hit. Commented Jun 18, 2022 at 22:08

2 Answers 2

2

As Tobias has said, you need something to survive to runtime to pass to your generic function. I recommend using a simple generic class:

class Route<T> {
  constructor(readonly path: string) {
  }

  transform(data: unknown): T[] {
    // unsafe cast by default; subclasses could do something more
    return data as T[];
  }
}

const personnelRoute = new Route<PersonnelDto>('/personnel/personnel');

async function getAll<T>(route: Route<T>): Promise<T[]> {
  return route.transform(await doFetch(route.path));
}
Sign up to request clarification or add additional context in comments.

2 Comments

Can I improve this with enum somehow? So I don't need to send the route? So function call is getAll(Entity.Personnel) and then somehow in the Route class if I send in Entity.Personnel it's declared that it has a path of '/personnel/personnel'?
@JamesG: That's functionally identical to this. If you want to organize your route constants in an object constant named Entity, nothing's stopping you. const Entity = { Personnel: new Route<PersonnelDto>(...), Asset: ... }. Though a caveat is that your routes will become heavily centralized. I don't see enums themselves as delivering much value here.
1

It is possible to utilize generics here to infer the return type based on something passed into the function.

You could create an interface like this:

interface Entities {
  '/assets/asset': AssetDto[],
  '/personnel/personnel': PersonnelDto[]
} 

With this interface, we can create a generic function which returns the correct type based on the passed route.

async getGeneric<T extends keyof Entities>(route: T){
  return await this.get<Entities[T]>(route)
} 

async otherFn() {
  const a = await this.getGeneric('/assets/asset')
  //    ^? AssetDto[]
  const b = await this.getGeneric('/personnel/personnel')
  //    ^? PersonnelDto[]
}

Playground


Or use an enum:

enum Routes {
  Asset = '/assets/asset',
  Personnel= '/personnel/personnel'
}

interface Entities {
  [Routes.Asset]: AssetDto[],
  [Routes.Personnel]: PersonnelDto[]
} 
async getGeneric<T extends keyof Entities>(route: T){
  return await this.get<Entities[T]>(route)
} 

async howIWantAFunctionCallToBeLike() {
  const a = await this.getGeneric(Routes.Asset)
  //    ^? AssetDto[]
  const b = await this.getGeneric(Routes.Personnel)
  //    ^? PersonnelDto[]
  }

Playground

1 Comment

Is it possible to use enums with this? So I can call the function with await this.getGeneric(Entity.Asset) so I don't need to keep track of routes when making function calls? If I have an enum like ```` const enum Ent { Asset = '/assets/assets' }```` They will atleast have identical string and if I could somehow ''link'' them together by it?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.