combineLatest correct syntax - syntax

I'm trying to use combineLatest, in particular I need to combine four Observables that emit values of different types with a projection function that returns a boolean type.
From what I get from the Rxjs documentation here, I think that this is the combineLatest signature I need to use, which is not deprecated:
combineLatest(sources: readonly any[], resultSelector: (...values: A) => R): Observable<R>
with R being boolean in my case.
Here is my code snippet where I try to use that function, but Visual Studio Code shows the call with strikeout style and it suggests it's deprecated.
this.canBook$ = combineLatest(
this.userSvc.canRedirect(this.fbSvc.getUserId()),
this.userSvc.canBook(this.fbSvc.getUserId()),
this.calSvc.segnaleOrarioIso8601(),
this.selectedDay$,
(canredir: boolean, canbook: boolean, iso8601: string, selday: ICGiorno) => {
return canredir && (dayjs(selday.iso8601).diff(iso8601, 'hour') > 0) ||
canbook && (dayjs(selday.iso8601).diff(iso8601, 'hour') >= 24);
} );
VS Code says:
The signature '(v1: Observable<boolean>, v2: Observable<boolean>, v3: Observable<string>, v4: Subject<ICGiorno>, resultSelector: (v1: boolean, v2: boolean, v3: string, v4: ICGiorno) => boolean, scheduler?: SchedulerLike): Observable<...>' of 'combineLatest' is deprecated.ts(6387)
However I'm not passing in any scheduler parameter, so I don't understand why VS Code is matching my call with the deprecated signature instead of the one documented above in the Rxjs API doc.
Can you please help me understand why?

As mentioned in the comments, the overload you're using is deprecated in RxJS#6.6.7 combineLatest, and there is no overload without scheduler, but instead, there are some overloads with scheduler, which are deprecated, e.g:
/** #deprecated resultSelector no longer supported, pipe to map instead */
export function combineLatest<O1 extends ObservableInput<any>, R>(sources: [O1], resultSelector: (v1: ObservedValueOf<O1>) => R, scheduler?: SchedulerLike): Observable<R>;
/** #deprecated resultSelector no longer supported, pipe to map instead */
export function combineLatest<O extends ObservableInput<any>, R>(sources: O[], resultSelector: (...args: ObservedValueOf<O>[]) => R, scheduler?: SchedulerLike): Observable<R>;
But in RxJS#latest combineLatest you can find the overload you mentioned in your question, and it's not deprecated, but the problem in your code is that you're using another overload than mentioned one, which is deprecated also:
/** #deprecated Pass an array of sources instead. The rest-parameters signature will be removed in v8. Details: https://rxjs.dev/deprecations/array-argument */
export function combineLatest<A extends readonly unknown[], R>(
...sourcesAndResultSelector: [...ObservableInputTuple<A>, (...values: A) => R]
): Observable<R>;
So, to fix it you just need to use the overload you mentioned correctly (which is not deprecated in RxJS#latest) like the following:
this.canBook$ = combineLatest(
[ // <<<< The mentioned overload expects an array not Observables as params.
this.userSvc.canRedirect(this.fbSvc.getUserId()),
this.userSvc.canBook(this.fbSvc.getUserId()),
this.calSvc.segnaleOrarioIso8601(),
this.selectedDay$,
], // <<<<
(
canredir: boolean,
canbook: boolean,
iso8601: string,
selday: ICGiorno
) => {
return (
(canredir && dayjs(selday.iso8601).diff(iso8601, 'hour') > 0) ||
(canbook && dayjs(selday.iso8601).diff(iso8601, 'hour') >= 24)
);
}
);

I would recommend keeping operators (like functions) simple by letting them do one thing. Regardless of what features may/or may not be deprecated in combineLatest(), you can use it just to combine the observables. After that, you can use a map() operator to return your boolean value.
this.canBook$ = combineLatest([
this.userSvc.canRedirect(this.fbSvc.getUserId()),
this.userSvc.canBook(this.fbSvc.getUserId()),
this.calSvc.segnaleOrarioIso8601(),
this.selectedDay$,
]).pipe(
map(
([canredir, canbook, iso8601, selday]) =>
(canredir && dayjs(selday.iso8601).diff(iso8601, "hour") > 0) ||
(canbook && dayjs(selday.iso8601).diff(iso8601, "hour") >= 24)
)
);

Related

Why does mergeMap flatten if the identity is passed?

I am trying to understand why Rxjs' mergeMap flattens when the identity function is passed in
of(['click', 'tap'])
.pipe(
mergeMap(_ => _),
tap(console.log) // Gets called twice, first with 'click' then with 'tap'
)
.subscribe();
Whereas in this case it is not:
of(['click', 'tap'])
.pipe(
mergeMap(_ => Promise.resolve(_)),
tap(console.log) // Gets called once with ['click', 'tap']
)
.subscribe();
The mergeMap operator, like the concatMap and switchMap operators, takes a function of type (value: T, index: number) => O as a parameter, where O extends ObservableInput<any>.
This is different from a simpler operator, such as map, which takes a function of type (value: T, index: number) => R, where R is the type of the value returned by the function itself.
This implies that everything you return in the mergeMap function must extend from ObservableInput, which is simply:
type ObservableInput<T> = Observable<T> | InteropObservable<T> | AsyncIterable<T> | PromiseLike<T> | ArrayLike<T> | Iterable<T> | ReadableStreamLike<T>;
So when you use the identity function to return a string[], mergeMap will understand that value as ArrayLike<string>. The fact that it is flattening the array is to be expected, as it creates a Subscription for each value of the ArrayLike<string>.
In the case of Promise.resolve(_), it interprets it as a PromiseLike, that is, it creates a promise with the emitted value, which happens to be an array. It turns the Promise<string[]> into an Observable<string[]> internally. The result would be the same if you run the code below:
of(['click', 'tap'])
.pipe(
mergeMap((x) => of(x)),
tap(console.log)
)
.subscribe();
Hope I helped you! See https://rxjs.dev/api/operators/mergeMap for more information.

rxjs conditional startWith , endWith using iif operator

Still figuring rxjs out and its a great library (have to admit). Given that we have this source and booleans.
let source = [0,1,2,3,4,5,6,7,8,9];
let swEnabled = false;
let ewEnabled = false;
And we would like to conditionally enable startwith and endwith operators based on swEnabled and ewEnabled to the source.....
source.pipe(
iif(() => swEnabled,startWith(1000),),
iif(() => ewEnabled,endWith(1000),),
).subscribe(
(n)=>{
console.log(n);
});
but no joy..... anyone can provide an example of this? Maybe the approach is wrong. Or can you suggest some alternative approach ?
Thanks in advance
You can conditionally build an array of operators as follows:
import { MonoTypeOperatorFunction, of } from 'rxjs';
import { pipeFromArray } from 'rxjs/internal/util/pipe';
import { endWith, startWith } from 'rxjs/operators';
let source = of(0,1,2,3,4,5,6,7,8,9);
let swEnabled = false;
let ewEnabled = false;
let operators: MonoTypeOperatorFunction<number>[] = []
if (swEnabled) {
operators.push(startWith(1000))
}
if (ewEnabled) {
operators.push(endWith(1000))
}
source.pipe(pipeFromArray(operators)).subscribe(e => console.log(e))
There is currently an open issue about not being able to use the spread syntax inside the pipe. If that issue gets fixed, then you will no longer need the pipeFromArray function and the solution can be simplified to source.pipe(...operators).
Re iif: Note that the iif function is an Observable constructor function and not an operator function. That means that you cannot use iif as an operator inside pipe() as you have shown. By observing the RxJx reference page, you can see that there are two sections, amongst others, titled index and operators. Only the functions under operators can be used directly within the pipe(). The functions under index can be used to construct source Observables.
Re startWith and endWith: These two functions are operator functions (meant to be used inside pipe) and not Observables. The iif function expects an Observable as the second and third argument.
I was having the same question, but after trying different strategies I came up with this solution:
someObservable.pipe(
// anyOperator(),
condition ? conditionalOperator() : tap(() => {})
)
In this case, if condition is true, operatorA is added to pipe, if not, the tap(() => {}) is like a "noop" (anyone can mix the condition and parameters to fit any needs).

Missing `switchMap` and `flatMap` overloads containing 'resultSelector' in RxJS6

I'm thoroughly confused about the new RxJS 6 and compatibility package. Currently I have these installed (for Angular 6):
"rxjs": "^6.1.0",
"rxjs-compat": "^6.1.0",
I have been previously using this signature for switchMap (in RxJS5):
switchMap(
project: function: Observable,
resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable
But when updating to version 6 only this overload is available (without the resultSelector parameter):
export declare function switchMap<T, R>(this: Observable<T>,
project: (value: T, index: number) => ObservableInput<R>): Observable<R>;
I'm importing it with
import { switchMap } from 'rxjs/operators';
How can I get access to this overload?
Also I tried to look at \node_modules\rxjs\operators\switchMap.d.ts to see what signatures were in there but the contents of this file is :
export * from 'rxjs-compat/operators/switchMap';
So it looks like the RxJS main package references the compatibility package? How does that make sense? Does the compatibility package actually modify RxJS - or has my package been corrupted - all the files show 1985 date stamps too!
Like I said I'm very confused! What's going on?
The overload signatures for switchMap that include a result selector are available with v6 with or without rxjs-compat installed. It's just that those overloads are deprecated, as they will be removed in version 7.
In version 6, they can still be used; the deprecation is just a warning of an upcoming change.
From rxjs/internal/operators/switchMap.d.ts:
import { ObservableInput, OperatorFunction } from '../types';
export declare function switchMap<T, R>(project: (value: T, index: number) => ObservableInput<R>): OperatorFunction<T, R>;
/** #deprecated resultSelector is no longer supported, use inner map instead */
export declare function switchMap<T, R>(project: (value: T, index: number) => ObservableInput<R>, resultSelector: undefined): OperatorFunction<T, R>;
/** #deprecated resultSelector is no longer supported, use inner map instead */
export declare function switchMap<T, I, R>(project: (value: T, index: number) => ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
The purpose of rxjs-compat is not to change the rxjs installation, rather rxjs delegates to rxjs-compat for functionality that has been removed.
For example, when rxjs-compat is installed, v5-style imports will still work. That is, this will be fine with rxjs-compat installed:
import { switchMap } from 'rxjs/operators/switchMap';
Whereas, without rxjs-compat installed, that import statement would effect an error.
For more information, see the migration guide.

Usage of non-argument variables in rxjs operators

Let's say there is function that returns observable, and the function has an argument
getObservable(arg){
return obs$.pipe(map(data => { // wanna use arg here }));
}
I am probably being paranoid here but it always confuses me if I can use arg inside map's callback and therefore I use combineLatest or withLatestFrom to send arg inside operator which feels redundant, but I am not sure.
getObservable(arg){
return combineLatest(of(arg), obs$).pipe(
map(([arg, data]) => {})
);
}
Could there be any problem if I go with the first way?

Typescript rest parameter in the middle of arguments list

I would like to declare a function which last parameter is always a callback. However when i do:
interface Statement extends events.EventEmitter {
bind(...args, callback?:(err?:Error)=>void) : Statement;
}
I get an error
error TS1014: Rest parameter must be last in list
Is it possible in typescript to heve Rest parameter not as a last param in the argument list? Is there any hack that could help me solve this problem?
While a rest parameter needs to be the last one, you can now use variadic tuple types in TS 4.0:
type VarCallback<T extends any[]> = (...args: [...T, (err?: Error) => void]) => void
VarCallback ensures, the last function parameter is a callback type (err?: Error) => void.
For example, declare a function type with the first two parameters string and number:
type MyCallback = VarCallback<[string, number]>
// (a1: string, a2: number, a3: (err?: Error | undefined) => void) => void
const myCb: MyCallback = (s, n, cb) => {/* your implementation */ }
// s,n,cb are strongly typed now
Live code sample
This isn't supported in TypeScript. The best you can do is ...args: any[], or only use libraries with more sensible parameter orderings.
The TypeScript spec for the rest parameter is aligned with ES6's: it is the last arg in the param list. You should change your argument order.
from TypeScript Language Spec (#Parameter List):
A signature’s parameter list consists of zero or more required parameters, followed by zero or more
optional parameters, finally followed by an optional rest parameter.
from ES6: rest parameters - Semantics:
The last parameter in the [[FormalParameters]] List is used for the rest parameter. Of the standard built-in ECMAScript objects, only Function objects implement [[HasRestParameters]].

Resources