mkHun
mkHun

Reputation: 5927

How to call the API in sequential order in Angular9 with rxjs?

I am trying to call the API in sequential order and I will use the previous response for the next API call. And I will be having the N number of API calls, Lets consider below is my sample array I have,

data = [
    {
        api: '/api/allGeneTables/',
    },
    {
        api: '/api/tables/',
        fieldName: 'profiles', // I am using this for building api
        concatWithPrevField: true,
        prevField: 'positions' // I am using this for building api
    },
    {
        api: '/api/tables/superpositions',
        fieldName: 'identical', // I am using this for building api
        concatWithPrevField: true,
        prevField: 'length' // I am using this for building api
    }
]

First API will produce the data like this

https://myurl/api/allGeneTables/
API Response:
{
    'positions': 10,
    'index': 12,
    'name': 'triosephaspate'
}

I need to create the next api with the previous value (positions) along with given fieldName (profiles), so the the second api would be

https://myurl/api/tables/10/profiles
// 10 came from the previous response value (positions)
// profiles is one of field's value of the current array

API Response: 
{
    'lipidprofile': 15,
    'maxvalue': 12,
    'name': 'triosephaspate',
    'length':232
}

similar way I need to create the next API.

https://myurl/api/tables/superpositions/232/identical
// 232 came from the previous response value (length)
// identical is one of field's value of the current array

I have tried with the map operator and following is my code

from(data).pipe(map(res => {
  const eachObj: any = res;
  let urlConcat = '';
  console.log(res);
  if (eachObj.concatWithPrevField) {
    // urlConcat = eachObj.api + ;
  } else {
    urlConcat = eachObj.api;
  }
  return this.dhs.getGeneData(urlConcat).subscribe();
})).subscribe();

I stuck at, how to join the previous API response value with the current data to make the API URL so I can call the next API. I am trying this with the RXJS methods.

Upvotes: 0

Views: 1815

Answers (4)

Rob Monhemius
Rob Monhemius

Reputation: 5144

  • Store the result from an API-call in a function scoped variable (last_result).
  • Use concatMap to wait for the API calls being completed before the next one is initiated and use the new value in last_result.
  • Use reduce to complete the observable stream and emit the latest API-response (last_result).

Stackblitz: https://stackblitz.com/edit/rxjs-v5pg4n?file=index.ts

import { from, interval } from "rxjs";
import { concatMap, map, reduce, take } from "rxjs/operators";

const data_source = [
  {
    api: "/api/allGeneTables/"
  },
  {
    api: "/api/tables/",
    fieldName: "profiles", // I am using this for building api
    concatWithPrevField: true,
    prevField: "positions" // I am using this for building api
  },
  {
    api: "/api/tables/superpositions/",
    fieldName: "identical", // I am using this for building api
    concatWithPrevField: true,
    prevField: "length" // I am using this for building api
  }
];

call_sequence(data_source).subscribe(console.log);

function call_sequence(data) {
  let last_result = null;
  return from(data).pipe(
    // concatmap awaits finishing of request ( last_result is being set and emits the API response)
    concatMap(fake_fetch),
    // reduce only emit last result from the stream
    reduce(() => last_result)
  );

  function fake_fetch(data) {
    let endpoint = data.api;
    if (data.concatWithPrevField) {
      endpoint = endpoint.concat(`${last_result[data.prevField]}`);
      endpoint = endpoint.concat(`/${data.fieldName}`);
    }
    return fake_API_call(endpoint).pipe(
      map(data => {
        last_result = data;
        return data;
      })
    );
  }
}

function fake_API_call(endpoint) {
  console.log(`making API call to ${endpoint}`);
  return interval(1000).pipe(
    take(1),
    map(data => {
      switch (endpoint) {
        case "/api/allGeneTables/":
          return {
            positions: 10,
            index: 12,
            name: "triosephaspate"
          };
        case "/api/tables/10/profiles":
          return {
            lipidprofile: 15,
            maxvalue: 12,
            name: "triosephaspate",
            length: 232
          };
        case "/api/tables/superpositions/232/identical":
          return "Hurray, I am the final result";
        default:
          console.error("This endpoint doesn't exist");
      }
    })
  );
}

Upvotes: 0

eko
eko

Reputation: 40647

You could use the expand operator for this type of operation. expand recursively calls the provided function

I will give a simple example and you can do the rest:

const data = [{ id: 1 }, { id: 2 }];
const source = from(data);
source
  .pipe(
    //recursively call supplied function
    expand((val, i) => {
      console.log(`Passed value: `, val, i);
      // prepare your next value here
      return of(data[i+1]);
    }),
    //call N times (data.length)
    take(data.length)
  )
  .subscribe(val => console.log(`RESULT: `, val));

stackblitz to play around: https://stackblitz.com/edit/typescript-kfkrga?file=index.ts&devtoolsheight=100

Upvotes: 1

munleashed
munleashed

Reputation: 1685

You should go with recursion in order to make this work for N number (not to hardcode things...). Easier to understand is to do this with Promises:

// Array of apis
const data: Array<{ api: string, fieldName?: string, concatWithPrevField?: boolean, prevField?: string }> = [];

// Create api call (convert it to a promise)
const createApiCall = (url) => {
    return this.dhs.getGeneData(url).toPromise();
};

const overall = data.reduce((prevCall, nextAPI) => {

    return prevCall.then((prevResult) => {
        let url = nextAPI.api;
        if (nextAPI.concatWithPrevField) {
            url += `${prevResult[nextAPI.prevField]}/${nextAPI.fieldName}`
        }

        return createApiCall(url);
    });


}, Promise.resolve());

// Subscribe to start...
overall.then(() => {

});

Ofc same can be done wih Observaables (with same approach or expand operator)

Upvotes: -1

BizzyBob
BizzyBob

Reputation: 14750

In general, to use results from one request to form and execute another request, you can chain them together using switchMap:

function httpCall_1(id) { ... }
function httpCall_2(id) { ... }
function httpCall_3(id) { ... }

myFinalData$ = httpCall_1('abc').pipe(
  switchMap(resp1 => httpCall_2(resp1.id)),
  switchMap(resp2 => httpCall_3(resp2.id))
);

If calls further down the chain need access to prior responses, you can nest like this:

function httpCall_1(id) { ... }
function httpCall_2(id) { ... }
function httpCall_3(id1, id2) { ... }

myFinalData$ = httpCall_1('abc').pipe(
  switchMap(resp1 => httpCall_2(resp1.id).pipe(
    switchMap(resp2 => httpCall_3(resp1.id, resp2.id))
  ))
);

Upvotes: 2

Related Questions