Encadenando Observables RxJS de datos http en Angular2 con TypeScript

95

Actualmente estoy tratando de aprender Angular2 y TypeScript después de trabajar felizmente con AngularJS 1. * durante los últimos 4 años. Tengo que admitir que lo odio, pero estoy seguro de que mi momento eureka está a la vuelta de la esquina ... de todos modos, he escrito un servicio en mi aplicación ficticia que obtendrá datos http de un backend falso que escribí y que sirve JSON.

import {Injectable} from 'angular2/core';
import {Http, Headers, Response} from 'angular2/http';
import {Observable} from 'rxjs';

@Injectable()
export class UserData {

    constructor(public http: Http) {
    }

    getUserStatus(): any {
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.get('/restservice/userstatus', {headers: headers})
            .map((data: any) => data.json())
            .catch(this.handleError);
    }

    getUserInfo(): any {
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.get('/restservice/profile/info', {headers: headers})
            .map((data: any) => data.json())
            .catch(this.handleError);
    }

    getUserPhotos(myId): any {
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.get(`restservice/profile/pictures/overview/${ myId }`, {headers: headers})
            .map((data: any) => data.json())
            .catch(this.handleError);
    }

    private handleError(error: Response) {
        // just logging to the console for now...
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }   
}

Ahora, en un componente, deseo ejecutar (o encadenar) ambos métodos getUserInfo()y getUserPhotos(myId). En AngularJS esto fue fácil ya que en mi controlador haría algo como esto para evitar la "Pirámide de la perdición" ...

// Good old AngularJS 1.*
UserData.getUserInfo().then(function(resp) {
    return UserData.getUserPhotos(resp.UserId);
}).then(function (resp) {
    // do more stuff...
}); 

Ahora he intentado hacer algo similar en mi componente (reemplazando .thena .subscribe) sin embargo mi consola de errores volverse loco!

@Component({
    selector: 'profile',
    template: require('app/components/profile/profile.html'),
    providers: [],
    directives: [],
    pipes: []
})
export class Profile implements OnInit {

    userPhotos: any;
    userInfo: any;

    // UserData is my service
    constructor(private userData: UserData) {
    }

    ngOnInit() {

        // I need to pass my own ID here...
        this.userData.getUserPhotos('123456') // ToDo: Get this from parent or UserData Service
            .subscribe(
            (data) => {
                this.userPhotos = data;
            }
        ).getUserInfo().subscribe(
            (data) => {
                this.userInfo = data;
            });
    }

}

Obviamente estoy haciendo algo mal ... ¿cómo sería mejor con Observables y RxJS? Lo siento si estoy haciendo preguntas estúpidas ... ¡pero gracias por la ayuda de antemano! También he notado el código repetido en mis funciones al declarar mis encabezados http ...

Mike Sav
fuente

Respuestas:

137

Para su caso de uso, creo que el flatMapoperador es lo que necesita:

this.userData.getUserPhotos('123456').flatMap(data => {
  this.userPhotos = data;
  return this.userData.getUserInfo();
}).subscribe(data => {
  this.userInfo = data;
});

De esta forma, ejecutará la segunda solicitud una vez que se reciba la primera. El flatMapoperador es particularmente útil cuando desea utilizar el resultado de la solicitud anterior (evento anterior) para ejecutar otra. No olvide importar el operador para poder usarlo:

import 'rxjs/add/operator/flatMap';

Esta respuesta podría brindarle más detalles:

Si solo desea usar el subscribemétodo, use algo como eso:

this.userData.getUserPhotos('123456')
    .subscribe(
      (data) => {
        this.userPhotos = data;

        this.userData.getUserInfo().subscribe(
          (data) => {
            this.userInfo = data;
          });
      });

Para finalizar, si desea ejecutar ambas solicitudes en paralelo y recibir una notificación cuando todos los resultados sean entonces, debe considerar usar Observable.forkJoin(debe agregar import 'rxjs/add/observable/forkJoin'):

Observable.forkJoin([
  this.userData.getUserPhotos(),
  this.userData.getUserInfo()]).subscribe(t=> {
    var firstResult = t[0];
    var secondResult = t[1];
});
Thierry Templier
fuente
Sin embargo, aparece el error 'TypeError: source.subscribe is not a function in [null]'
Mike Sav
¿Dónde tienes este error? Al llamar this.userData.getUserInfo()?
Thierry Templier
3
Oh! Hubo un error tipográfico en mi respuesta: en this.userData.getUserPhotos(), this.userData.getUserInfo()lugar de this.userData.getUserPhotos, this.userData.getUserInfo(). ¡Lo siento!
Thierry Templier
1
¿Por qué flatMap? ¿Por qué no cambiarMap? Asumiría que si la solicitud GET inicial de repente genera otro valor para el Usuario, no querrá seguir obteniendo imágenes para el valor anterior de Usuario
f.khantsis
4
Para cualquiera que vea esto y se pregunte por qué no funciona: en RxJS 6, la sintaxis de importación y forkJoin ha cambiado ligeramente. Ahora necesita agregar import { forkJoin } from 'rxjs';para importar la función. Además, forkJoin ya no es un miembro de Observable, sino una función separada. Entonces, en lugar de Observable.forkJoin()simplemente forkJoin().
Bas