Search code examples
angularrxjs

RxJS how to use combineLatest with startWith with multiple values?


I have a question about using combineLatest with the startWith operator

The code below works

const data_1$ = new Subject<string>();
const data_2$ = new Subject<number>();

viewModel$ = combineLatest([data_1$, data_2$])
   .pipe(map([data_1, data_2]) => {
      return { data_1, data_2 };
   })

however now if I want to use startWith operator

const data_1$ = new Subject<string>();
const data_2$ = new Subject<number>();

viewModel$ = combineLatest([data_1$, data_2$])
   .pipe(
      startWith(["hello", 1]),
      map([data_1, data_2]) => {
         return { data_1, data_2 };
      })

The map part will infer that data_1 and data_2 can be string | number. Can I get it to infer as the first example which doesn't use startWith? I know about BehaviorSubject and it could be used instead of startWith but in my case I need to use startWith.


Solution

  • Starting from the official documentation, startWith's typing is given by:

    startWith<T, D>(...values: D[]): OperatorFunction<T, T | D>
    

    In your case, you want to ensure that startWith only returns something of the type [string, number].

    The issue occurs because when you provide the array with ['hello',1] it considers the type as (string|number)[]. This is because in the generic declaration, only one type, D[] is provided. Hence why it considers the type of ['hello',1] to be (string|number)[], which fits D[].

    To solve this, you can provide the typing explicitly with the use of the generic:

    startWith<[string, number]>(['hello', 1])
    

    The question then arises, why does map infer the argument type currently? Well the answer to this is also in the official typing of map:

    map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R>
    

    In map's case, it infers the type from the the provided value T. Since there is no need to match any array casting, the value of [string, number] is used instead.