user1354784
user1354784

Reputation: 406

Typescript generic class that implements a generic type

Is there a way to turn the following piece of code into generic typescript code? I want to have a class that implements some interface A and that takes two elements of type A and returns different values for the properties of interface A depending on some boolean flag. I looked at typescript generics and custom types but couldn't figure it out.

Essentially, I want an element to return different values in portrait and landscape without having to resort to things like mySprite.setPosition(new Vector(10,10)) each time the orientation of my application changes. I am asking this out of curiosity, I am not looking for other solutions to this situation, I know there are many more.

Could I define some generic type, say Variant such that for any property of type A my sprite has, I could do something like mySprite.myPropOfTypeA=new Variant<A>(new A("landscape value"), new A("portrait value")) so that mySprite.myPropOfTypeA.value returns different values depending on the outcome of isLandscape()? I want this to be generic so that I don't have to create a derived class for each property I want to behave like this. Thanks!

interface IVector{
    x:number,
    y:number;
}

class Vector implements IVector{
    private _x:number;
    private _y:number;

    public get x() {return this._x;}
    public get y() {return this._y;}

    constructor(x:number, y:number){
        this._x=x;
        this._y=y;
    }
}

class VariableVector implements IVector{

    private _landscape: IVector;
    private _portrait: IVector;

    constructor(landscape: IVector, portrait: IVector){
        this._landscape=landscape;
        this._portrait=portrait;
    }

    public get x() {return isLandscape()? this._landscape.x:this._portrait.x;}
    public get y() {return isLandscape()? this._landscape.y:this._portrait.y;}

}

let mySprite=new Sprite(new VariableVector(new Vector(0,0), new Vector(10,10)));

Thanks!

Upvotes: 4

Views: 5548

Answers (2)

jcalz
jcalz

Reputation: 330086

The ability to programmatically alter all property reads on A in the same way isn't really something a class gives you (especially since there's no way to extend a generic type... class Variant<A> extends A is invalid). What you really want to use in this case is a Proxy. At the risk of giving you an answer you don't want (not sure if this violates "I am not looking for other solutions to this situation"), here's one way you could make such Proxy objects:

function makeVariant<A extends object>(
  condition: () => boolean, 
  trueValue: A, 
  falseValue: A
): A {
  return new Proxy(trueValue, {
    get(target, prop: keyof A, receiver) {
      return condition() ? trueValue[prop] : falseValue[prop];
    }
  });
}

The function makeVariant accepts a callback and two values of generic type A, and returns an object whose properties will be drawn from one or the other value depending on whether the callback returns true or false. I'm not sure if you want the scope of the callback function to be outside of the definition of makeVariant so it doesn't need to be passed in as a parameter, but you could probably fiddle with that if you need to.

Let's see if it works:

// not sure if isLandscape() is meant to be global    
let landscape: boolean = true;
function isLandscape() {
  return landscape;
}

const variantVector = makeVariant(isLandscape, new Vector(1, 2), new Vector(3, 4));

landscape = true;
console.log(variantVector.x, variantVector.y) // 1, 2
landscape = false;
console.log(variantVector.x, variantVector.y) // 3, 4

Looks reasonable to me. Hope that points you in a useful direction. Good luck!

Upvotes: 3

shokha
shokha

Reputation: 3189

I guess I understand you correctly :)

class IPosition {
  constructor(public x: number, public y: number, public value: string) {}
}

class Variant<T extends IPosition> {
  constructor(private _landscape: T, private _portrait: T) {}

  public get x() { return isLandscape() ? this._landscape.x: this._portrait.x; }
  public get y() { return isLandscape()? this._landscape.y:this._portrait.y; }
}

const myPropOfTypeA = new Variant<IPosition>(new IPosition(0, 0, "landscape value"), new IPosition(10, 10, "portrait value"));

Feel free to ask questions in comments, will try to change the example in order to fit your task.

Also created a stackblitz example

Upvotes: 1

Related Questions