Washington Guedes
Washington Guedes

Reputation: 4365

How to build an observable which calls multiple observables based on previous result?

I have one endpoint which returns { ids: [1, 2, 3, 45] }.

And another one which returns a value for a given id: { id: 3, value: 30, active: true }.

I'm trying to construct an observable which calls the first endpoint and for each id returned call the second enpoint and emits the sum of all active = true values:

private getIds(group: string) {
  const url = '...';
  return this.http.get<{ ids: number[] }>(url, { params: { group } });
}

private getValue(id: number) {
  const url = '...';
  return this.http.get<ActiveValue>(url, { params: { id: id.toString() } });
}

public getSum(group: string) {
  let sum = 0;
  const bSubject = new BehaviorSubject(sum);

  const observables = this.getIds(group).pipe(
    mergeMap(({ ids }) => ids),
    map(id => this.getValue(id).pipe(tap(({ value, active }) => {
      if (active) {
        sum += value;
        bSubject.next(sum);
      }
    })))
  );

  const observable = forkJoin(observables).pipe(map(() => sum));
  return { bSubject, observable };
}

interface ActiveValue {
  value: number;
  active: boolean;
}

But it complains about:

forkJoin is deprecated: Use the version that takes an array of Observables instead (deprecation)

Also, when I hover over observables it shows:

const observables: Observable<Observable<ActiveValue>>

...while I thought it should be Observable<ActiveValue>[]

How can I make it work?

Upvotes: 1

Views: 1552

Answers (2)

Jacek Rojek
Jacek Rojek

Reputation: 1122

Maybe something like:

const data = [
  { id: 1, value: 30, active: true },
  { id: 2, value: 10, active: false },
  { id: 3, value: 5, active: true },
  { id: 45, value: 1, active: false }
]

function getIds(group) {
    const promise = new Promise(function(resolve, reject) {
     setTimeout(function() {
        const resp = { ids: [1, 2, 3, 45] }
       resolve(resp.ids);
     }, 100);
   });
   return Rx.Observable.fromPromise(promise)
   
}

function getValue(id) {
        const promise = new Promise(function(resolve, reject) {
     setTimeout(function() {
       resolve(data.find(x => x.id == id));
     }, 100);
   });
   return Rx.Observable.fromPromise(promise)
}

const reduceSum = (acc, {active, value}) => acc += active ? value : 0

function getSum(group) {
  return this.getIds(group)
    .mergeMap(ids => {
        return Rx.Observable.from(ids)
        .mergeMap(id => this.getValue(id))
        .reduce(reduceSum, 0)
    })
}

getSum().subscribe(console.log)
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>

Upvotes: 1

Max Ivanov
Max Ivanov

Reputation: 163

I not shure but you may try something like this

interface ActiveValue {
    value: number;
    active: boolean;
}

function countActiveValues(values: ActiveVale[]) {
    return values.reduce((acc, { value, active }) => acc + active ? value : 0, 0)
}

class MyClass {
    private getIds(group: string) {
        const url = '...';
        return this.http.get < { ids: number[] } > (url, { params: { group } });
    }

    private getValue(id: number) {
        const url = '...';
        return this.http.get < ActiveValue > (url, { params: { id: id.toString() } });
    }

    private getRequests(ids: number[]) {
        return ids.map((id) => this.getValue(id));
    }

    public getSum(group: string) {
        return this.getIds(group).pipe(
            map(({ ids }) => this.getRequests(ids)),
            switchMap((requests) => forkJoin(requests)),
            map((results) => countActiveValues(result))
        );
    }
}

And don't forget to catchError for your requests ;)

Upvotes: 3

Related Questions