Generic constraints

Sometimes, we don't need the concrete type required by a function, class, or method, but we know that such type must adhere to a certain set of rules.

For example, the following code snippet declares a generic function named isEquals. However, this time the type T has a constraint (T extends Comparable):

interface Comparable<T> {
equals(value: T): boolean;
}

function isEqual<TVal, T extends Comparable<TVal>>(comparable: T, value: TVal) {
return comparable.equals(value);
}

The constraint is used to ensure that all the types provided to isEqual as its generic type argument implement the Comparable interface:

interface RectangleInterface {
width: number;
height: number;
}

type ComparableRectangle = RectangleInterface & Comparable<RectangleInterface>;

class Rectangle implements ComparableRectangle {
public width: number;
public height: number;
public constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
public equals(value: Rectangle) {
return value.width === this.width && value.height === this.height;
}
};

interface CircleInterface {
radious: number;
}

type ComparableCircle = CircleInterface & Comparable<CircleInterface>;

class Circle implements ComparableCircle {
public radious: number;
public constructor(radious: number) {
this.radious = radious
}
public equals(value: CircleInterface): boolean {
return value.radious === this.radious;
}
}

const circle = new Circle(5);
const rectangle = new Rectangle(5, 8);

isEqual<RectangleInterface, ComparableRectangle>(rectangle, { width: 5, height: 8 });
isEqual<CircleInterface, ComparableCircle>(circle, { radius: 5 });