Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript discriminated union types with Observable.of

I'm trying to use Typescript 2.0's discriminated union types with RxJS, but I'm getting an error that the object I am returning is not of the one of the types of the union type.

Here are my types:

interface Square {
  kind: "square";
  width: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

interface Center {
  kind: "center";
}

type Shape = Square | Circle | Center;

This function where I just return a Shape not using an Observable compiles completely fine:

function shapeFactory(width: number): Shape {
  if (width > 5) {
    return {kind: "circle", radius: width};
  } else if (width < 2) {
    return {kind: "square", width: 3};
  }

  return {kind: "center"};
}

When I instead try to return an Observable<Shape> like so:

function shapeFactoryAsync(width: number): Observable<Shape> {
  if (width > 5) {
    return Observable.of({kind: "circle", radius: width});
  } else {
    return Observable.of({kind: "center"});
  }
}

I am met with the compilation error:

Type 'Observable<{ kind: string; radius: number; }>' is not assignable to type 'Observable<Shape>'.
  Type '{ kind: string; radius: number; }' is not assignable to type 'Shape'.
    Type '{ kind: string; radius: number; }' is not assignable to type 'Center'.
      Types of property 'kind' are incompatible.
        Type 'string' is not assignable to type '"center"'.

I expect that my first return would be of type Observable<{ kind: "circle"; radius: number; }>, since kind is the discriminate across all Shape types. Strangely, it is okay with Observable.of({kind: "center"}), possibly because there's no other data associated with it?

I am able to fix it if I explicitly assign the object and give the assignment a type like so:

let circle: Circle = {kind: "circle", radius: width};
return Observable.of(circle);

Though this seems like it should be an unnecessary cast.

Am I just doing this completely wrong or is that cast necessary in order to figure out that kind is supposed to be of value "circle" instead of type string?

like image 624
lambdabutz Avatar asked Jan 03 '17 23:01

lambdabutz


People also ask

What is discriminated unions in TypeScript?

A discriminated type union is where you use code flow analysis to reduce a set of potential objects down to one specific object. This pattern works really well for sets of similar objects with a different string or number constant for example: a list of named events, or versioned sets of objects.

Can I check a type against a union type in TypeScript?

We can differentiate between types in a union with a type guard. A type guard is a conditional check that allows us to differentiate between types. And in this case, a type guard lets us figure out exactly which type we have within the union.

What is tagged union in TypeScript?

Creating Tagged Unions in TypeScript Introduction to Tagged Unions A tagged union is a data structure that holds several different data types, each of them distinguishable from one another using a discriminating property (usually called a “tag”).

When would you use an intersection type instead of a union type?

— Union types can hold any instance of its components but can't use functions of one. It can only use properties defined in all its components. — Intersection types can hold a subset of its components instances but can use functions of any of them.


1 Answers

With a call like Observable.of({ kind: "center" }), TypeScript is unable to infer the type from the anonymous argument.

You can solve your problem by specifying the type variable as Shape when calling the generic of method:

function shapeFactoryAsync(width: number): Observable<Shape> {
  if (width > 5) {
    return Observable.of<Shape>({ kind: "circle", radius: width });
  } else {
    return Observable.of<Shape>({ kind: "center" });
  }
}

With the type variable specified, TypeScript no longer has to infer the type.

like image 78
cartant Avatar answered Nov 15 '22 08:11

cartant