Tworzenie łańcuchów RxJS Observables z danych http w Angular2 za pomocą TypeScript

96

Obecnie próbuję nauczyć się Angular2 i TypeScript po szczęśliwej pracy z AngularJS 1. * przez ostatnie 4 lata! Muszę przyznać, że go nienawidzę, ale jestem pewien, że mój moment eureki jest tuż za rogiem ... w każdym razie napisałem usługę w mojej fałszywej aplikacji, która pobierze dane http z fałszywego zaplecza, które napisałem, obsługującego 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');
    }   
}

Teraz w komponencie chcę uruchomić (lub połączyć) obie metody getUserInfo()i getUserPhotos(myId). W AngularJS było to łatwe, ponieważ w moim kontrolerze zrobiłbym coś takiego, aby uniknąć "Piramidy zagłady" ...

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

Teraz próbowałem robić coś podobnego w moim składnika (zastępując .thenna .subscribe) jednak moja konsola błędów wariuje!

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

}

Oczywiście robię coś źle ... jak bym najlepiej radził sobie z Observables i RxJS? Przepraszam, jeśli zadaję głupie pytania ... ale z góry dziękuję za pomoc! Zauważyłem również powtarzający się kod w moich funkcjach podczas deklarowania moich nagłówków http ...

Mike Sav
źródło

Odpowiedzi:

138

Myślę, że w twoim przypadku flatMappotrzebujesz operatora:

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

W ten sposób zrealizujesz drugie żądanie po odebraniu pierwszego. flatMapOperator jest szczególnie przydatna, gdy chcesz użyć wynik poprzedniego żądania (poprzednim przypadku), aby wykonać kolejne. Nie zapomnij zaimportować operatora, aby móc go używać:

import 'rxjs/add/operator/flatMap';

Ta odpowiedź może dać ci więcej szczegółów:

Jeśli chcesz używać tylko subscribemetody, używasz czegoś takiego:

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

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

Na koniec, jeśli chcesz wykonać oba żądania równolegle i być powiadomionym, gdy wszystkie wyniki są wtedy, powinieneś rozważyć użycie Observable.forkJoin(musisz dodać 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
źródło
Jednak pojawia się błąd „TypeError: source.subscribe nie jest funkcją w [null]”
Mike Sav
Gdzie masz ten błąd? Dzwoniąc this.userData.getUserInfo()?
Thierry Templier
3
O! W mojej odpowiedzi była literówka: this.userData.getUserPhotos(), this.userData.getUserInfo()zamiast this.userData.getUserPhotos, this.userData.getUserInfo(). Przepraszam!
Thierry Templier
1
dlaczego flatMap? Dlaczego nie zmienić mapy? Zakładam, że jeśli początkowe żądanie GET nagle wyprowadzi inną wartość dla użytkownika, nie chciałbyś nadal otrzymywać obrazów dla poprzedniej wartości użytkownika
f.khantsis
4
Dla każdego, kto patrzy na to i zastanawia się, dlaczego to nie działa: w RxJS 6 składnia import i forkJoin uległa nieznacznej zmianie. Teraz musisz dodać, import { forkJoin } from 'rxjs';aby zaimportować funkcję. Ponadto forkJoin nie jest już członkiem Observable, ale oddzielną funkcją. Więc zamiast Observable.forkJoin()tego po prostu forkJoin().
Bas