Konrad Viltersten
Konrad Viltersten

Reputation: 39190

Retain the type of mutiple Observable<T> using combineLatest() from rxjs under Angular?

When my component loads, I need to consume two services. Both need to be finished before it makes sense to continue. The order of completion of those is random and shouldn't be composed serially. The setup follows the pattern below.

const observables = [
  this.donkeyService.getDonkey(),
  this.monkeyService.getMonkey()
];

combineLatest(observables)
  .subscribe(([donkeyResult, monkeyResult]) => {
    if (!!donkeyResult && !!monkeyResult) {
      ...
    }
  }

We've noticed that the results aren't typed as expected. It took a while before we realized it, but the type of donkeyResult isn't Donkey, despite the definition of the server below!

getDonkey() : Observable<Donkey> { ... }

Finally, I realized that when the elements are delivered to the receiving array, they lose their typeness, since the array itself is any[]. So we manage it using casting as follows.

const observables = [
  this.donkeyService.getDonkey(),
  this.monkeyService.getMonkey()
];

combineLatest(observables)
  .subscribe(([_1, _2]) => {
    const donkeyResult = _1 as Donkey;
    const monkeyResult = _2 as Monkey;

    if (!!donkeyResult && !!monkeyResult) {
      ...
    }
  }

I'd like to refactor the code so that the array will retain the types specified by the service methods' signatures and won't coalesce into a common denominator any.

Is it possible to make TypeScript and/or Rxjs to have an array where the first element would be a Donkey and the second Monkey? Could I use a different data structure than an array?

I tried (and failed, of course) a bunch of different approaches including horsing around by casting the types directly in the array like so.

...
.subscribe(([_1 as Donkey, _2 as Monkey]) => { ... }

Is there a neat way to retain typeness? We're open to changing the approach altogether as we're owning this part of software and got some extra time to massage the code.

Upvotes: 17

Views: 9708

Answers (3)

Francesco Borzi
Francesco Borzi

Reputation: 61954

using the map() operator

For those using the map and having the same typing problem, I solved passing the types to map using the diamond operators.

combineLatest([monkey, donkey].pipe(
  map<[Monkey, Donkey], ResultType>().(([monkey, donkey]) => {
    // here the variables 'monkey' and 'donkey' are correctly typed
  })
);

Upvotes: 1

Dale Harris
Dale Harris

Reputation: 607

This sounds like a use case for tuples. https://visualstudiomagazine.com/articles/2016/02/01/type-safe-structures.aspx?m=1

type animalTuple = [Donkey, Monkey];
.subscribe(([donkeyResult, monkeyResult]: animalTuple) => { ... }

Upvotes: 3

user4676340
user4676340

Reputation:

First things first, you have to type your function and your HTTP call.

getMonkey(): Observable<Donkey> {
  return this.http.get<Donkey>(url);
}

Once done, you have to type your array of observables :

const observables: [Monkey, Donkey] = [
  this.getMonkey(),
  this.getDonkey(),
];

Finally, although not necessary, you can type your callback params :

forkJoin(observables).subscribe(([monkey, donkey]: [Monkey, Donkey]) => {...});

Upvotes: 20

Related Questions