0

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

4
  • You are able to pass any into derived class methods because they hide the base method. This means that the declared type is actually that of the find methods in the derived classes. Commented Apr 28, 2017 at 9:38
  • that's what I thought / was hoping - but then why is the compiler allowing me to pass "foobar" as one of the properties of IProjectParams ? Commented Apr 28, 2017 at 9:42
  • If Project has the type of the class, the same reasoning applies. Commented Apr 28, 2017 at 9:46
  • FWIW I would just use a single function and some interfaces. Commented Apr 28, 2017 at 9:55

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

wow. thanks - I have been trying to use this structure . However, I've hit a couple of problems: 1) the 'find' method is returning a promise, so shouldn't the signature be " Promise<Array<TEntity>>", and #2 with unit tests and mocha, I now get an error compiling unit tests with "error TS2339: Property 'rejectedWith' does not exist on type 'Assertion'." where my test is "Project.find({token:"invalidToken"}).should.be.rejectedWith({statusCode: 401});" - but a "Project.find({token:"validToken"}).then((data) => {" works just fine
Yeah, the return type should be as you said. About the error in the tests, I guess could be something related to the type definitions of this library.
Installing "@types/chai-as-promised" provides the type definitions needed by typescript.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.