I have the following code
let obs$: Observable<string>
obs$.pipe(
tap(data => {
// do stuff
}),
);
If I use VSCode and hover over data variable, I see that this is of type string as expected.
Then I add ingnoreElements after tap
obs$.pipe(
tap(data => {
// do stuff
}),
ignoreElements()
);
Now, if I hover over data, type inference tells me that data is of type any.
This seems like a retroactive inference. Which is the reason behind this type inference behavior?
Here a stackblitz to reproduce this situation.
THE ANSWER provided by the response from @Andrei Gătej
The whole mystery lays in the way ignoreElements() is declared, i.e.
export declare function ignoreElements(): OperatorFunction<any, never>;
Now, you place ignoreElements() after an operator like tap(), which is declared like this
export declare function tap<T>(observer: PartialObserver<T>): MonoTypeOperatorFunction<T>;
and basically within pipe() you have a chain such as this
MonoTypeOperatorFunction<T>,
OperatorFunction<any, never>
which pipe() interprets as: "T" is a generic type, "any" is a real type, then since ignoreElements() expects "any" then "T" must be of type "any"
And this explains the mystery.
Now there are few possible solutions.
One way is to isolate ignoreElements() into a different pipe() like this
obs$.pipe(
tap(data => {
// do stuff
}),
)
.pipe(
ignoreElements()
);
If you do this the variable data is inferred of type string which is what we want.
A more elegant solution though would be to change the declaration of ignoreElements(). If you declare it like this
export declare function ignoreElements<T>(): OperatorFunction<T, never>;
everything works as expected without the need of doing any trick.
It may be worth to suggest a change in the declaration of ignoreElements().