jmls
jmls

Reputation: 2969

typescript classes, interfaces and generics

I am writing 2 classes, Project and User, each of which has a find method that needs to call an api rest call

attempt #1

class Project {
  find(params) { 
    return request({url: "/api/Project", qs: params});
  }
}

class User {
  find(params) { 
    return request({url: "/api/User", qs: params});
  }
}

now, this is obviously not very good ;) There are no checks on the parameters, no types defined, duplicate code etc etc

attempt #2

class Base {
  constructor(private name:string) {
  } 

  find(options) { 
    return request({url: `/api/${this.name}`, qs: params});
  }
}

class Project extends Base{
  constructor() { super("Project"); }
}

class User {
  constructor() { super("User"); }    
}

so, slightly better. less code duplication. Still no type checking. Interfaces to the rescue ;)

attempt#3

interface IParams { token: string } 

class Base {
  constructor(private name:string) {
  } 

  find(params:IParams) { 
    return request({url: `/api/${this.name}`, qs: params});
  }
}

class Project extends Base{
  constructor() { super("Project"); }
}

class User extends Base {
  constructor() { super("User"); }    
}

this is where I started to hit some problems. The Project and User api params object both require the token property. However, they also require userDd and projectId to be set

At the moment, I need to add both of those to the IParams interface, which seems wrong.

attempt#4

interface IUserParams { userid:number, token: string } 
interface IProjectParams { projectid:number, token: string } 

interface IProject {
   find(params:IProjectParams)
}

interface IUser {
   find(params:IUserParams)
}

class Base {
  constructor(private name:string) {
  } 

  find(params) { // I have no idea on how to "type" params
    return request({url: `/api/${this.name}`, qs: params}); // likewise no idea on how to type the return value
  }
}

class Project extends Base implements IProject {
  constructor() { super("Project"); }
}

class User extends Base implements IUser {
  constructor() { super("User"); }    
}

However, this does not help : as the Base class defines the find method, how can the compiler verify that for user, userid and token are passed - also, that no other parameter is passed, and likewise for project

This also led me onto thinking about the return value of the find method : for projects I want an array of IPromiseModel, and for user, IUserModel

I have tried chaning the IProject interface to read

interface IProject {
    find(params:IProjectParams):Promise<IProjectModel[]>
}

but I still can pass any property into the params - ie I can do

Project.find({token: "1234",foobar:true})

I suspect this is because I haven't defined a type for the parameter in the Base find

I know that generics must play a part in this, but for the life of me I cannot get a definition working that matches these requirements

I am using typescript 2.2.2

Upvotes: 0

Views: 204

Answers (1)

Thiago Barcala
Thiago Barcala

Reputation: 7323

With generics you can do this:

interface IParams { token: string }
interface IUserParams { userid:number, token: string }
interface IProjectParams { projectid:number, token: string }

class Base<TEntity, TParams extends IParams> {
    constructor(private name:string) {
    }

    find(params: TParams): Promise<TEntity[]> {
        return request({url: `/api/${this.name}`, qs: params});
    }
}

class Project extends Base<Project, IProjectParams> {
    constructor() { super("Project"); }
}

class User extends Base<User, IUserParams> {
    constructor() { super("User"); }
}

new User().find({
    userid: 123,
    token: 'test'
});

The constraint in the Base class TParams extends IParams here is optional, since you are not explicitly accessing the token property.

Upvotes: 2

Related Questions