Typescript supports discriminated unions. How to extend the same concept with Rxjs to the filter
operator in below example?
interface Square {
kind: 'square';
width: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
interface Center {
kind: 'center';
}
type Shape = Square | Circle | Center;
const obs$: Observable<Shape> = of<Shape>({ kind: 'square', width: 10 });
// Expected type: Observable<Square>
// Actual type: Observable<Shape>
const newObs$ = obs$.pipe(
filter((x) => x.kind === 'square')
);
I above code snippet, I would want to see newObs$ to have its type inferred as: Observable<Square>
. But apparently, TypeScript
doesn't do that.
How to achieve this? Am I reaching the limits of TypeScript type inference?
I look for this as it seems to be very useful in a Redux + Redux-Observable
codebase.
The concept of discriminated unions is how TypeScript differentiates between those objects and does so in a way that scales extremely well, even with larger sets of objects. As such, we had to create a new ANIMAL_TYPE property on both types that holds a single literal value we can use to check against.
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.
In TypeScript, we can define a variable which can have multiple types of values. In other words, TypeScript can combine one or two different types of data (i.e., number, string, etc.) in a single type, which is called a union type.
Actually you can do this with TypeScript type guards. See section "Type Guards and Differentiating Types" at http://www.typescriptlang.org/docs/handbook/advanced-types.html
The key here is the function isWhatever(x: any): x is Whatever => ...
syntax.
This basically says that if the isWhatever
function returns true
then it guarantees that x
is of Whatever
type.
In your example TypeScript considers all three classes:
So you can define the predicate function for filter()
as this:
filter((x: Shape): x is Square => x.kind === 'square')
Now it will properly consider only the Square
class:
See live demo: https://stackblitz.com/edit/rxjs6-demo-z9lwxe?file=index.ts
Very similar question: https://github.com/ReactiveX/rxjs/issues/2340
This is not necessarily a limitation with the TypeScript type system but rather with the implementation of filter
. You can easily achieve the desired behaviour using flatMap
:
// Inferred type: Observable<Square>
const newObs$ = obs$.pipe(
flatMap((x) => x.kind === "square" ? of(x) : empty())
);
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