Concatenamento di osservabili RxJS da dati http in Angular2 con TypeScript


96

Attualmente sto cercando di insegnare a me stesso Angular2 e TypeScript dopo aver lavorato felicemente con AngularJS 1. * negli ultimi 4 anni! Devo ammettere che lo odio ma sono sicuro che il mio momento eureka è proprio dietro l'angolo ... comunque, ho scritto un servizio nella mia app fittizia che recupererà i dati http da un backend fasullo che ho scritto che serve 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');
    }   
}

Ora in un componente desidero eseguire (o concatenare) entrambi i metodi getUserInfo()e getUserPhotos(myId). In AngularJS questo è stato facile perché nel mio controller avrei fatto qualcosa del genere per evitare la "Piramide del destino" ...

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

Ora ho provato a fare qualcosa di simile nel mio componente (sostituendo .thenper .subscribe), tuttavia la mia console di errore sta impazzendo!

@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;
            });
    }

}

Ovviamente sto facendo qualcosa di sbagliato ... come potrei meglio con Observables e RxJS? Scusa se ti sto facendo domande stupide ... ma grazie per l'aiuto in anticipo! Ho anche notato il codice ripetuto nelle mie funzioni quando dichiaro le mie intestazioni http ...

Risposte:


138

Per il tuo caso d'uso, penso che l' flatMapoperatore sia ciò di cui hai bisogno:

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

In questo modo, eseguirai la seconda richiesta una volta ricevuta la prima. L' flatMapoperatore è particolarmente utile quando si desidera utilizzare il risultato della richiesta precedente (evento precedente) per eseguirne un'altra. Non dimenticare di importare l'operatore per poterlo utilizzare:

import 'rxjs/add/operator/flatMap';

Questa risposta potrebbe darti maggiori dettagli:

Se vuoi usare solo il subscribemetodo, usi qualcosa del genere:

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

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

Per finire, se desideri eseguire entrambe le richieste in parallelo e ricevere una notifica quando tutti i risultati sono quindi, dovresti considerare di utilizzare Observable.forkJoin(devi aggiungere import 'rxjs/add/observable/forkJoin'):

Observable.forkJoin([
  this.userData.getUserPhotos(),
  this.userData.getUserInfo()]).subscribe(t=> {
    var firstResult = t[0];
    var secondResult = t[1];
});

Tuttavia ricevo l'errore "TypeError: source.subscribe non è una funzione in [null]"
Mike Sav

Dove hai questo errore? Quando chiami this.userData.getUserInfo()?
Thierry Templier

3
Oh! C'era un errore di battitura nella mia risposta: this.userData.getUserPhotos(), this.userData.getUserInfo()invece di this.userData.getUserPhotos, this.userData.getUserInfo(). Scusa!
Thierry Templier

1
perché flatMap? Perché non switchMap? Presumo che se la richiesta GET iniziale emette improvvisamente un altro valore per Utente, non vorresti continuare a ricevere immagini per il valore precedente di Utente
f.khantsis

4
Per chiunque lo guardi e si chieda perché non funziona: in RxJS 6 la sintassi di importazione e forkJoin è leggermente cambiata. Ora è necessario aggiungere import { forkJoin } from 'rxjs';per importare la funzione. Inoltre, forkJoin non è più un membro di Observable, ma una funzione separata. Quindi invece di Observable.forkJoin()è solo forkJoin().
Bas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.