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:
tsc
will fail, sayingerror TS2339: Property 'connect' does not exist on type 'Observable<any>'.
. Casting theconnectableObservable
toany
and calling.connect()
works as expected.Edit: Used latest typescript 2.8.3.
Actually, if the overload signatures for
pipe
are 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
ConnectableObservable
in the signtures for a method inObservable
seems a little weird, asConnectableObservable
extendsObservable
, but this is only way I can see this working.An import of
ConnectableObservable
would be necessary inObservable.ts
, but as it’s only used in the typings, the import is erased during transpilation.The signatures for
publish
andpublishLast
would have to be changed, too, as it they do not specifyConnectableObservable
in their return types.Interestingly, adding such overload signatures to
pipe
seems 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
pipe
approach? 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:
publish
and notpublishReplay
publishReplay
does NOT return a connected observable so no issue with thepipe
therepublish()
in a pipe completely loses both the connectable nature of the observable and its type. (Typescript limitation from an earlier version AFAIK).publish
typescript 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
publish
is most useful you’ll often have several ‘child’ streams that take the output of your primary source stream and transform it. So just by usingpublish
in thepipe
you 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
publish
operator 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
connect
to 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 callconnect
AND 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.ConnectableObservable
instances 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 theexperimental
branch.Workaround:
EDIT: that’s wrong, it will just type the connectable (see @simeyla comment below)