angular – How can I sort an array of `n` elements, which are obtained with` n` asynchronous requests?

Question:

Context

We have a sale view on the front that looks like this:

<table>
  <tbody>
    <tr *ngFor="let offer of offerList">
      <td>{{offer[0].appArea.creationDate}}</td>
      <td>{{offer[0].client.id}}</td>
      <td>{{offer[0].client.name}}</td>
      ...
    </tr>
  </tbody>
</table>

This offerlist is collected from a database in a less traditional way: only the ids and a hash are stored in the database, with which an IPFS network is accessed, which is the one that has the data of the offers.

Furthermore, it is set up in such a way that, to obtain an array of n offers, exactly n + 1 requests are made: the first one brings the array with the ids and hashes, and the next n are the result of iterating through that array and asking the IPFS data:


export class OffersComponent implements OnInit{

  offerList: Array<any> = []

  loadOffersForClient(email) {

    this.offerService.getOwnerInitialOffers(email).subscribe(
      (initOfferResponse) => {
        initOfferResponse[0].offers.map((item) => {

          this.offerService.getOfferFileData(item.hashId).subscribe(
            (itemResponse) => {
              this.offerList.push([itemResponse, item, ]);
            }
          );
        });
      }
    );
  }
}

The service makes simple calls with the http client:

export class OfferService {

  constructor(private http: HttpClient) { }

  getOwnerInitialOffers(email: string) {
    return this.http.post<any>(`path/to/offers/${email}`); // se traen datos por post porque asi esta especificada la api
  }

  getOfferFileData(hashId: string) {
    return this.http.get<any>(`path/to/file/server/${hashId}`);
  }
}

Trouble

The requirements have changed and we need to sort the array. By date, to be more exact. The problem comes because I am not sure when I can order the array being sure that the elements have finished filling .

Until now, it had occurred to me to order it by brute force, each time a new request is launched:

this.offerService.getOfferFileData(item.hashId).subscribe(
  (itemResponse) => {
    this.offerList.push([itemResponse, item, ]);
    this.offerList.sort((a, b) => {
      const firstDate = a[0].applicationArea.creationDateTime;
      const secondDate = b[0].applicationArea.creationDateTime;
      return (firstDate < secondDate) ? 1 : ((firstDate > secondDate) ? -1 : 0);
    });
  }
);

Aside from being incredibly inefficient it doesn't work, I understand that the items haven't been loaded by the time the sort occurs.

How can I make sure that the array has already been loaded, so that I can sort only once and without danger of new elements entering?

Answer:

You can use the RxJs function forkJoin :

import 'rxjs/add/observable/forkJoin';
...


let observables=[];
observables.push(this.http.get(URL1));
observables.push(this.http.get(URL2));
observables.push(this.http.get(URL3));
observables.push(this.http.get(URL4));
forkJoin(observables, arrayOfValues => { this.offerList = arrayOfValues.sort(...)});

Working example:

const { from,forkJoin } = rxjs;
const {map} = rxjs.operators;

//Por simplificar, creo un Observable desde una promesa
function getObservableAjax(url) {
  return from(fetch(url).then(resp => resp.json()))
      .pipe(map(elem => elem.name)); //de cada respuesta, nos quedamos con el campo name
}

function getCharactersFromStarWars() {
    let observables=[];
    for (let i = 1; i < 10; i++) {
      observables.push(getObservableAjax(`https://swapi.dev/api/people/${i}/`));
    }
    return forkJoin(observables);
}

getCharactersFromStarWars().subscribe(elements => {
  elements.sort(); 
  console.log(elements);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.7/rxjs.umd.min.js" integrity="sha512-0/2ebe9lI6BcinFBXFjbBkquDfccT2wP+E48pecciFuGMXPRNdInXZawHiM2NUUVJ4/aKAzyebbvh+CkvRhwTA==" crossorigin="anonymous"></script>
Scroll to Top