Solvedrxjs Pipe operator cannot infer return type as ConnectableObservable
✔️Accepted Answer
This issue still exists in rxjs 6.0 final. Reproduce:
const connectableObservable = from([1, 2, 3]).pipe(publish());
connectableObservable.connect()
tsc
will fail, saying error TS2339: Property 'connect' does not exist on type 'Observable<any>'.
. Casting the connectableObservable
to any
and calling .connect()
works as expected.
Edit: Used latest typescript 2.8.3.
Other Answers:
Actually, if the overload signatures for pipe
are written like this:
pipe<A, OA extends Observable<A>>(op1: UnaryFunction<Observable<T>, OA>): OA;
pipe<A, B, OB extends Observable<B>>(op1: OperatorFunction<T, A>, op2: UnaryFunction<Observable<A>, OB>): OB;
// etc.
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.
The problem can be solved by adding additional overload signatures to pipe
, allowing for the last operator to return a ConnectableObservable
.
For example:
pipe<A>(op1: UnaryFunction<Observable<T>, ConnectableObservable<A>>): ConnectableObservable<A>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
Using ConnectableObservable
in the signtures for a method in Observable
seems a little weird, as ConnectableObservable
extends Observable
, but this is only way I can see this working.
An import of ConnectableObservable
would be necessary in Observable.ts
, but as it's only used in the typings, the import is erased during transpilation.
The signatures for publish
and publishLast
would have to be changed, too, as it they do not specify ConnectableObservable
in their return types.
Interestingly, adding such overload signatures to pipe
seems to fix another problem with the inference of the publishReplay
. Without them, the return type of the following:
const o = of(0).pipe(publishReplay(1));
is inferred as Observable<any>
(note the any
) and with them it's inferred as ConnectableObservable<number>
.
@ebrehault this looks like (type)safer workaround
#3595
upd: actually that can be wrapped into observable factory function to provide better type inference and more traditional shape: Observable<T> => ConnectedObservable<T>
// NB! Remember that with such import you probably would like tree-shaking
import { publish } from 'rxjs/operators';
export function _publish<T>(source: Observable<T>): ConnectableObservable<T> {
return publish<T>()(source);
}
upd:
import { UnaryFunction } from 'rxjs/interfaces';
export function _publish<T, R>(
source: Observable<T>,
publishFn: UnaryFunction<Observable<T>, ConnectableObservable<R>>
): ConnectableObservable<R> {
return publishFn(source);
}
// use:
// NB! Remember that with such import you probably would like tree-shaking
import { publishReplay } from 'rxjs/operators';
declare const foo$: Observable<number>;
_publish(foo$, publishReplay(1)); // inferred to ConnectableObservable<number>
@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 for pipe
, it means that that type-safe long pipelines are impossible? Can this topic be addressed in v7?
RxJS version: 5.5.0
Code to reproduce:
Expected behavior: The inferred type of
connectableObs
isConnectableObservable<number>
.Actual behavior: The inferred type of
connectableObs
isObservable<number>
.Additional information:
TypeScript version: 2.5.3
My current workaround is to manually downcast the return value:
Happy to send a PR, but not sure how should I fix this properly.