3

I have the following classes:

class Walls { }
class Furniture { }
class Layout<T extends Walls | Furniture> { }
class Space extends Layout<Walls> { }
class Room extends Layout<Furniture> { }

I need to create these two classes:

class SpaceController extends LayoutController<Space> { }
class RoomController extends LayoutController<Room> {}

To do this, I can't create LayoutController class like this:

class LayoutController<T extends Layout>{ }

because Layout needs a Type parameter.

I can instead create this:

class LayoutController<U, T extends Layout<U extends Walls | Furniture>>{ }

but that would mean I will have to do this:

class SpaceController extends LayoutController<Walls, Space> { }
class RoomController extends LayoutController<Furniture, Room> {}

which I feel is redundant. Moreover, it opens up room for errors. There's nothing stopping me from writing:

class RoomController extends LayoutController<Walls, Room> {}

How do I solve this?

More details about LayoutController:

class LayoutController<T> extends React.Component<{}, LayoutControllerState<T>>() { }
interface LayoutControllerState<T> { 
  selectedLayout: T;
}
5
  • Could you provide more details on implementation of LayoutController? It seems that it could be defined as class LayoutController<T extends Walls | Furniture> but maybe I'm missing something. Commented Aug 11, 2018 at 6:50
  • @AlekseyL. it is a React component that has a field called selected layout in state. ``` class LayoutController<T> extends React.Component<{}, LayoutControllerState<T>>() { } interface LayoutControllerState<T> { selectedLayout: T; } ``` Commented Aug 11, 2018 at 7:15
  • OK, so you can type inner layout filed as Layout<T> where T is generic parameter of LayoutController Commented Aug 11, 2018 at 7:19
  • @AlekseyL. formatting is broken in comments. Please see the edit in the question itself. Commented Aug 11, 2018 at 7:20
  • @AlekseyL.that would mean I have to write class SpaceController extends LayoutController<Walls> { }, not LayoutController<Space> Commented Aug 11, 2018 at 7:23

1 Answer 1

5

While a bit more typing a two type parameter solution is not bad and will give you appropriate errors if U is not compatible with the T expected by layout, if type constraints are properly specified:

class Walls { height!: number; }
class Furniture { price!: number; }
class Layout<T extends Walls | Furniture> { children: T[] = []; }
class Space extends Layout<Walls> { private x: undefined; }
class Room extends Layout<Furniture> { private x: undefined; }

class LayoutController<U extends Walls | Furniture, T extends Layout<U>>{
    getValue(u: U) : void{}
}

class SpaceController extends LayoutController<Walls, Space> { }
class RoomController extends LayoutController<Furniture, Room> {}
class ErrController extends LayoutController<Walls, Room> {}  //Type 'Room' does not satisfy the constraint 'Layout<Walls>

We can use a conditional type to extract the generic parameter from the Layout type and provide this as default for U. Thus we don't have to specify the redundant parameter:

type ExtractLayoutParameter<T extends Layout<any>> = T extends Layout<infer U> ? U: never;
class LayoutController<T extends Layout<any>, U extends Walls | Furniture= ExtractLayoutParameter<T>>{
    getValue(u: U) : void{}
}

class SpaceController extends LayoutController<Space> { }
class RoomController extends LayoutController<Room> {}
new SpaceController().getValue(new Walls())
new SpaceController().getValue(new Furniture()) // error

We could also use the conditional type instead of U thus not allowing the user to change U to a derived type of that accepted by the layout (depeding on your use case a feature or a design limitation you decide):

type ExtractLayoutParameter<T extends Layout<any>> = T extends Layout<infer U> ? U: never;
class LayoutController<T extends Layout<any>>{
    getValue(u: ExtractLayoutParameter<T>) : void{}
}

class SpaceController extends LayoutController<Space> { }
class RoomController extends LayoutController<Room> {}
new SpaceController().getValue(new Walls())
new SpaceController().getValue(new Furniture()) // error
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your detailed response! Didn't know there's so much in Typescript :) I'll read up more before I use conditional types, infer etc. But your two parameter solution is simple for now. Thanks!
@AbdulsattarMohammed And there is plenty more in 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.