rxjs: Pipe operator cannot infer return type as ConnectableObservable
RxJS version: 5.5.0
Code to reproduce:
const obs = Observable.of(5);
const connectableObs = obs.pipe(
publishReplay(1)
);
Expected behavior: The inferred type of connectableObs is ConnectableObservable<number>.
Actual behavior: The inferred type of connectableObs is Observable<number>.
Additional information:
TypeScript version: 2.5.3
My current workaround is to manually downcast the return value:
const obs = Observable.of(5);
const connectableObs = obs.pipe(
publishReplay(1)
) as ConnectableObservable<number>;
Happy to send a PR, but not sure how should I fix this properly.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 65
- Comments: 17 (3 by maintainers)
Commits related to this issue
- Correct typings issues in pipe Allows specific types of Observables to be passed through (as in #2972), and also prevents incorrect typings from falling through to ellipsis function due to the use of... — committed to plaffey/rxjs by deleted user 6 years ago
- Correct typings issues in pipe Allows specific types of Observables to be passed through (as in #2972), and also prevents incorrect typings from falling through to ellipsis function due to the use of... — committed to plaffey/rxjs by deleted user 6 years ago
- Correct typings issues in pipe Allows specific types of Observables to be passed through (as in #2972), and also prevents incorrect typings from falling through to ellipsis function due to the use of... — committed to plaffey/rxjs by deleted user 6 years ago
- fix(Observable): correct typings issues in pipe Allows specific types of Observables to be passed through (as in #2972), and also prevents incorrect typings from falling through to ellipsis function ... — committed to plaffey/rxjs by deleted user 6 years ago
This issue still exists in rxjs 6.0 final. Reproduce:
tscwill fail, sayingerror TS2339: Property 'connect' does not exist on type 'Observable<any>'.. Casting theconnectableObservabletoanyand calling.connect()works as expected.Edit: Used latest typescript 2.8.3.
Actually, if the overload signatures for
pipeare written like this:The problem can be solved without specific reference to
ConnectableObservable.This seems to be okay with the version of TypeScript (2.0) that RxJS uses, but I’ve not investigated this thoroughly.
Seems it’s still an issue. Is there any update related to this?
The problem can be solved by adding additional overload signatures to
pipe, allowing for the last operator to return aConnectableObservable.For example:
Using
ConnectableObservablein the signtures for a method inObservableseems a little weird, asConnectableObservableextendsObservable, but this is only way I can see this working.An import of
ConnectableObservablewould be necessary inObservable.ts, but as it’s only used in the typings, the import is erased during transpilation.The signatures for
publishandpublishLastwould have to be changed, too, as it they do not specifyConnectableObservablein their return types.Interestingly, adding such overload signatures to
pipeseems to fix another problem with the inference of thepublishReplay. Without them, the return type of the following:is inferred as
Observable<any>(note theany) and with them it’s inferred asConnectableObservable<number>.@ebrehault this looks like (type)safer workaround https://github.com/ReactiveX/rxjs/issues/3595
upd: actually that can be wrapped into observable factory function to provide better type inference and more traditional shape:
Observable<T> => ConnectedObservable<T>upd:
@benlesh @cartant what do you say, is it possible to fix with the whole
pipeapproach? And another question regarding piping and types: since there’s a limited set of overload signatures forpipe, it means that that type-safe long pipelines are impossible? Can this topic be addressed in v7?I’m curious if this is considered a bug that’s going to get fixed? I am fine type casting but would prefer not to. I do want to point out though, that
pipe()has always only returnedObservable<T>in my experience – if I’m wrong there, cool, I just wonder if justifying having an operator transform the output type of the piped observable leads to justifying doing it for all types? What I mean by that is, I created a custom operator to convert an observable to a BehaviorSubject which gives caching. But this method requires the solution provided here – typecasting toBehaviorSubject<T>at the end of the pipe. This is “fine”, but ideally, if an operator is spitting out some subtype of Observable, that subtype, at least logically speaking, should be preserved.I got myself super confused by this whole issue, so just wanted to add a couple findings that may help others - especially in understanding what is not the problem:
publishand notpublishReplaypublishReplaydoes NOT return a connected observable so no issue with thepipetherepublish()in a pipe completely loses both the connectable nature of the observable and its type. (Typescript limitation from an earlier version AFAIK).publishtypescript strips away both the ConnectableObservable type AND the fundamental type of your source.In other words the type of
of(1,2,3).pipe(publish())isObservable<any>instead ofConnectableObservable<number>(which is what it really is).When
publishis most useful you’ll often have several ‘child’ streams that take the output of your primary source stream and transform it. So just by usingpublishin thepipeyou lose the ability inside your operators to know what your incoming observable is.@fljot’s answer is most useful to solve this problem because all it does it to call the
publishoperator and in isolation typescript can properly figure out all the types. The key usefulness of this method is that you don’t need to explicitly provide (or even know) the type of your source observable, so everything will ripple through nicely by itself.So you can publish a stream, use it to create other streams and then
connectto it.The only recommendation I have for usage is to call it on a separate line like this:
@ebrehault your answer is misleading. The
connect()method isn’t generic, so the goal is not to have a ‘typed connectable’ observable when you callconnect(), but a ‘connectable’ observable when you callconnectAND a ‘typed’ observable when you create further streams.@fljot In v7 it won’t be an issue, as the operators that will be pipeable won’t return a
ConnectableObservable. They will be like the variants that accept a selector.ConnectableObservableinstances will only be returned by static creator/factory functions. See https://github.com/ReactiveX/rxjs/issues/3833 for more information. Or look at the source in theexperimentalbranch.Workaround:
EDIT: that’s wrong, it will just type the connectable (see @simeyla comment below)