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
?
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.
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.
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”).
— 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With