Typescript discriminated union types with Observable.of

1.7k views Asked by At

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?

1

There are 1 answers

3
cartant On BEST ANSWER

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.