Home > database >  RxJS how to use combineLatest with startWith with multiple values?
RxJS how to use combineLatest with startWith with multiple values?

Time:01-05

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.

CodePudding user response:

Starting from the official documentation, startWith its typing is given by:

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

In your case, you want to ensure that starts with 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 explicit with the use of the generics:

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

The question that 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.

CodePudding user response:

You just have to declare the argument passed to the startWith operator with as const.

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

The expression as const makes the compiler treat the array as a readonly tuple. That's actually too specific for what you want to use in the map operator. However, the startWith operator won't return that tuple type, as it returns the union of the type of the argument and the type passed along the stream. That return type ends up being [string, number] - which is what you want.

  • Related