RxJS-waarneembare gegevens koppelen aan http-gegevens in Angular2 met TypeScript

Ik probeer mezelf momenteel Angular2 en TypeScript te leren na de afgelopen 4 jaar met veel plezier met AngularJS 1.* te hebben gewerkt! Ik moet toegeven dat ik er een hekel aan heb, maar ik ben er zeker van dat mijn eureka-moment om de hoek staat … hoe dan ook, ik heb een service in mijn dummy-app geschreven die http-gegevens zal ophalen van een nep-backend die ik heb geschreven en die JSON bedient.

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

Nu wil ik in een Component zowel getUserInfo()als getUserPhotos(myId)methoden uitvoeren (of koppelen). In AngularJS was dit gemakkelijk, omdat ik in mijn controller zoiets zou doen om de “Pyramid of doom” te vermijden…

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

Nu heb ik geprobeerd iets soortgelijks in mijn component te doen (vervanging van .thenvoor .subscribe), maar mijn foutconsole wordt gek!

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

Ik doe duidelijk iets verkeerd… hoe kan ik het beste met Observables en RxJS werken? Sorry als ik domme vragen stel… maar alvast bedankt voor de hulp! Ik heb ook de herhaalde code in mijn functies opgemerkt bij het declareren van mijn http-headers…


Antwoord 1, autoriteit 100%

Voor jouw gebruik denk ik dat de operator flatMapis wat je nodig hebt:

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

Op deze manier voer je het tweede verzoek uit zodra het eerste is ontvangen. De operator flatMapis vooral handig wanneer u het resultaat van het vorige verzoek (vorige gebeurtenis) wilt gebruiken om een ​​ander verzoek uit te voeren. Vergeet niet de operator te importeren om deze te kunnen gebruiken:

import 'rxjs/add/operator/flatMap';

Dit antwoord kan u meer details geven:

Als je alleen de subscribe-methode wilt gebruiken, gebruik je zoiets:

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

Ten slotte, als u beide verzoeken parallel wilt uitvoeren en een melding wilt ontvangen wanneer alle resultaten dan zijn, kunt u overwegen Observable.forkJointe gebruiken (u moet import 'rxjs/add/observable/forkJoin'):

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

Antwoord 2

Wat je eigenlijk nodig hebt, is de operator switchMap. Het neemt de eerste gegevensstroom (Gebruikersinfo) en wanneer voltooid, vervangt het deze door de waarneembare afbeeldingen.

Dit is hoe ik je flow begrijp:

  • Gebruikersinformatie ophalen
  • Gebruikers-ID uit die info halen
  • Gebruik de gebruikers-ID om gebruikersfoto’s te krijgen

Hier is een demo. OPMERKING: ik bespotte de service, maar de code werkt met de echte service.

 ngOnInit() {
    this.getPhotos().subscribe();
  }
  getUserInfo() {
    return this.userData.getUserInfo().pipe(
      tap(data => {
        this.userInfo = data;
      }))
  }
  getPhotos() {
    return this.getUserInfo().pipe(
      switchMap(data => {
        return this.userData.getUserPhotos(data.UserId).pipe(
          tap(data => {
            this.userPhotos = data;
          })
        );
      })
    );
  }

Other episodes