Menu

Peticiones en paralelo con RXJS (ForkJoin)

angular2tips - December 06, 2017 por Nicolas Molina | Edita este Post.
ionic3.9.2ionic-native4.4.2ionic-app-scripts3.1.2cordova-cli7.0.1ionic-cli3.19.0

Los Observables son pieza clave en Ionic y Angular, en artículos pasados vimos como consumir una REST API, ahora vamos a ver como hacer peticiones en paralelo usando Observables con ForkJoin.

Actualización (01/12/2017)


Hemos actualizado este demo con el último release Ionic 3.9 y Angular 5.

Ver demo


¿Qué son las peticiones en paralelo?

Hacer peticiones en paralelo, puede llegar a un caso muy usual cuando nos conectamos a una API REST y nos sirve para ejecutar más de una petición al mismo tiempo y tener los resultados de cada una por separado de forma síncrona.

Un ejemplo puede ser estas dos peticiones:

api.domain.com/api/v2/events que responde lo siguiente:

[
  {
    "id": 1,
    "title": "Evento 1",
    "date": "1470761987422",
    "speaker_id": 1
  },
  {
    "id": 2,
    "title": "Evento 2",
    "date": "1470761987422",
    "speaker_id": 2
  }
]

Y api.domain.com/api/v2/speakers que responde lo siguiente:

[
  {
    "id": 1,
    "name": "Nicolas Molina",
    "job": "FronEnd"
  },
  {
    "id": 2,
    "name": "Carlos Rojas",
    "job": "I don't know"
  }
]

Tenemos dos peticiones donde cada evento tiene relacionado un speaker, pero en nuestra aplicación queremos tener todo el evento completo en modo JSON con la relación de evento y speaker, de esta manera:

[
  {
    "id": 1,
    "title": "Evento 1",
    "date": "1470761987422",
    "speaker_id": 1,
    "speaker": {
      "id": 1,
      "name": "Nicolas Molina",
      "job": "FronEnd"
    }
  },
  {
    "id": 2,
    "title": "Evento 2",
    "date": "1470761987422",
    "speaker_id": 2,
    "speaker": {
      "id": 2,
      "name": "Carlos Rojas",
      "job": "I don't know"
    }
  }
]

Solución 1 (esto seria lo mejor):

Esta situación la tendríamos ya solucionada si nuestra API REST ya nos retorna la solicitud con la relación que nosotros necesitamos, pero no siempre tenemos en nuestras manos el desarrollo de la API REST y la implementación puede tardar (depende del caso).

Solución 2:

Ahora si no tenemos control del backend y de igual manera nos toca relacionar los datos podemos dejar este trabajo del lado del cliente, para esto necesitamos los métodos individuales de cada solicitud getEvents y getSpeakers y además una función que se encargue de relacionar los datos join. Existen dos maneras de abordar este problema y una es ejecutar una a una las peticiones (sin ForkJoin) y la segunda es ejecutar en paralelo las peticiones y recibir las respuestas al mismo tiempo (con ForkJoin).



Como vemos usando ForkJoin podemos ejecutar varias peticiones en paralelo y recibirlas de forma ordenada la respuesta de cada una. Lo cual hace que al usar ForkJoin podamos tener un código más mantenible y escalable.

Abajo encontrarán el código completo para cada caso:

Sin ForkJoin:


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class TestProvider {

  events: any[] = [];
  speakers:  any[] = [];

  constructor(private http: Http) {}

  getEvents(){
    return this.http.get('data/events.json')
    .toPromise();
  }

  getSpeakers(){
    return this.http.get('data/speakers.json')
    .toPromise();
  }

  getEventsWithSpeaker(){
    return this.getEvents().then(events =>{
      this.events = events;
      return this.getSpeakers();
    })
    .then(speakers =>{
      this.speakers = speakers;
      return Promise.resolve(this.join(this.events, this.speakers))
    })
    .catch(error => {
      return Promise.reject(error)
    })
  }

  join(events, speakers){
    return events.map(event => {
      return speakers
      .filter(speaker => speaker.id == event.speaker_id)
      .map(speaker => {
        return {
          id: event.id,
          title: event.title,
          date: event.date,
          speaker_id: event.speaker_id,
          speaker: speaker
        }
      })
    }).reduce((a,b) =>{
      return a.concat(b);
    }, []);
    
  }

}

Con ForkJoin:

La función ForJoin sirve tanto para ejecutar varias promesas al mismo tiempo como para Observables, en este caso vamos a usar el Observable que retorna un petición GET de Angular:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';

@Injectable()
export class TestProvider {

  events: any[] = [];
  speakers:  any[] = [];

  constructor(private http: HttpClient) {}

  getEvents(){
    return this.http.get('assets/data/events.json');
  }

  getSpeakers(){
    return this.http.get('assets/data/speakers.json');
  }

  getEventsWithSpeaker(){
    return Observable.forkJoin(
      this.getEvents(),
      this.getSpeakers()
    )
    .map(res => this.join( res[0], res[1] ))
  }

  join(events, speakers){
    return events.map(event => {
      return speakers
      .filter(speaker => speaker.id == event.speaker_id)
      .map(speaker => {
        return {
          id: event.id,
          title: event.title,
          date: event.date,
          speaker_id: event.speaker_id,
          speaker: speaker
        }
      })
    }).reduce((a,b) =>{
      return a.concat(b);
    }, []);

  }

}

Vamos a usar esta ultima version para implementarla como provider en la pagína HomePage:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

import { TestProvider } from '../../providers/test/test';

@IonicPage()
@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
})
export class HomePage {

  events: any[] = [];

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    private testProvider: TestProvider
  ) {
  }

  ionViewDidLoad() {
    this.testProvider.getEventsWithSpeaker()
    .subscribe(events =>{
      this.events = events;
    }, error => {
      console.log( error );
    })
  }

}

Y nuestro home.html, quedara así:


<ion-header>
  <ion-navbar color="primary">
    <ion-title>Demo114</ion-title>
  </ion-navbar>
</ion-header>
<ion-content>
    <ion-list>
      <ion-item *ngFor="let event of events">
        <h2>{{ event.title }}</h2>
        <p>Date {{ event.date | date }}</p>
        <p>Speaker: {{ event.speaker.name }}</p>
        <p>Job: {{ event.speaker.job }}</p>
      </ion-item>
    </ion-list>
</ion-content>

Resultado:


Ver código

Ver demo

Recuerda:

Si quieres ejecutar este demo en tu computadora, debes tener el entorno de ionic instalado y luego de descargar o clonar el proyecto debes ubicarte en tu proyecto en la terminal y luego ejecutar

npm install

esto es para descargar todas las dependencias del proyecto.

¡Compártelo!