Verketten von RxJS Observables aus http-Daten in Angular2 mit TypeScript

95

Ich versuche gerade, mir Angular2 und TypeScript beizubringen, nachdem ich in den letzten 4 Jahren glücklich mit AngularJS 1 * gearbeitet habe! Ich muss zugeben, dass ich es hasse, aber ich bin mir sicher, dass mein Eureka-Moment gleich um die Ecke ist ... trotzdem habe ich in meiner Dummy-App einen Dienst geschrieben, der http-Daten von einem falschen Backend abruft, das ich geschrieben habe und das JSON dient.

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

Jetzt möchte ich in einer Komponente sowohl getUserInfo()als auch getUserPhotos(myId)Methoden ausführen (oder verketten) . In AngularJS war dies einfach, da ich in meinem Controller so etwas tun würde, um die "Pyramide des Untergangs" zu vermeiden ...

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

Jetzt habe ich versuche , etwas zu tun , ähnlich wie in meiner Komponente ( als Ersatz .thenfür .subscribe) aber meine Fehler - Konsole verrückt zu werden!

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

}

Ich mache offensichtlich etwas falsch ... wie würde ich am besten mit Observables und RxJS umgehen? Entschuldigung, wenn ich dumme Fragen stelle ... aber danke für die Hilfe im Voraus! Ich habe auch den wiederholten Code in meinen Funktionen beim Deklarieren meiner http-Header bemerkt ...

Mike Sav
quelle

Antworten:

137

Für Ihren Anwendungsfall denke ich, dass der flatMapOperator genau das ist, was Sie brauchen:

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

Auf diese Weise führen Sie die zweite Anforderung aus, sobald die erste empfangen wurde. Der flatMapOperator ist besonders nützlich, wenn Sie das Ergebnis der vorherigen Anforderung (vorheriges Ereignis) verwenden möchten, um eine andere auszuführen. Vergessen Sie nicht, den Operator zu importieren, um ihn verwenden zu können:

import 'rxjs/add/operator/flatMap';

Diese Antwort könnte Ihnen weitere Details geben:

Wenn Sie nur die subscribeMethode verwenden möchten, verwenden Sie Folgendes:

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

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

Wenn Sie beide Anforderungen parallel ausführen und benachrichtigt werden möchten, wenn alle Ergebnisse vorliegen, sollten Sie die Verwendung in Betracht ziehen Observable.forkJoin(Sie müssen hinzufügen 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
quelle
Ich erhalte jedoch den Fehler 'TypeError: source.subscribe ist keine Funktion in [null]'
Mike Sav
Wo haben Sie diesen Fehler? Beim Anruf this.userData.getUserInfo()?
Thierry Templier
3
Oh! In meiner Antwort war ein Tippfehler: this.userData.getUserPhotos(), this.userData.getUserInfo()statt this.userData.getUserPhotos, this.userData.getUserInfo(). Es tut uns leid!
Thierry Templier
1
warum flatMap? Warum nicht switchMap? Ich würde annehmen, dass, wenn die anfängliche GET-Anfrage plötzlich einen anderen Wert für Benutzer ausgibt, Sie nicht weiterhin Bilder für den vorherigen Wert von Benutzer erhalten
möchten
4
Für alle, die sich das ansehen und sich fragen, warum es nicht funktioniert: In RxJS 6 haben sich die Import- und ForkJoin-Syntax geringfügig geändert. Sie müssen jetzt hinzufügen import { forkJoin } from 'rxjs';, um die Funktion zu importieren. Außerdem ist forkJoin kein Mitglied von Observable mehr, sondern eine separate Funktion. Also anstatt Observable.forkJoin()nur forkJoin().
Bas