Chaînage d'observables RxJS à partir de données http dans Angular2 avec TypeScript

96

J'essaie actuellement de m'enseigner Angular2 et TypeScript après avoir travaillé avec bonheur avec AngularJS 1. * pendant les 4 dernières années! Je dois admettre que je déteste ça, mais je suis sûr que mon moment eureka est juste au coin de la rue ... de toute façon, j'ai écrit un service dans mon application factice qui récupérera les données http d'un faux backend que j'ai écrit et qui sert 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');
    }   
}

Maintenant dans un composant, je souhaite exécuter (ou enchaîner) les deux méthodes getUserInfo()et getUserPhotos(myId). Dans AngularJS, c'était facile car dans mon contrôleur, je ferais quelque chose comme ça pour éviter la "Pyramid of doom" ...

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

Maintenant, j'ai essayé de faire quelque chose de similaire dans mon composant (en remplaçant .thenpour .subscribe) mais ma console d'erreur devient folle!

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

}

Je fais évidemment quelque chose de mal ... comment pourrais-je mieux avec Observables et RxJS? Désolé si je pose des questions stupides ... mais merci pour l'aide d'avance! J'ai également remarqué le code répété dans mes fonctions lors de la déclaration de mes en-têtes http ...

Mike Sav
la source

Réponses:

138

Pour votre cas d'utilisation, je pense que l' flatMapopérateur est ce dont vous avez besoin:

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

De cette façon, vous exécuterez la deuxième demande une fois la première reçue. L' flatMapopérateur est particulièrement utile lorsque vous souhaitez utiliser le résultat de la requête précédente (événement précédent) pour en exécuter une autre. N'oubliez pas d'importer l'opérateur pour pouvoir l'utiliser:

import 'rxjs/add/operator/flatMap';

Cette réponse pourrait vous donner plus de détails:

Si vous ne voulez utiliser que la subscribeméthode, vous utilisez quelque chose comme ça:

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

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

Pour finir, si vous souhaitez exécuter les deux requêtes en parallèle et être averti lorsque tous les résultats sont alors, vous devez envisager d'utiliser Observable.forkJoin(vous devez ajouter 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
la source
Cependant, j'obtiens l'erreur «TypeError: source.subscribe n'est pas une fonction dans [null]»
Mike Sav
Où avez-vous cette erreur? Lors de l'appel this.userData.getUserInfo()?
Thierry Templier
3
Oh! Il y avait une faute de frappe dans ma réponse: this.userData.getUserPhotos(), this.userData.getUserInfo()au lieu de this.userData.getUserPhotos, this.userData.getUserInfo(). Désolé!
Thierry Templier
1
pourquoi flatMap? Pourquoi pas switchMap? Je suppose que si la demande GET initiale génère soudainement une autre valeur pour User, vous ne voudriez pas continuer à obtenir des images pour la valeur précédente de User
f.khantsis
4
Pour tous ceux qui regardent cela et se demandent pourquoi cela ne fonctionne pas: dans RxJS 6, la syntaxe d'importation et de forkJoin a légèrement changé. Vous devez maintenant ajouter import { forkJoin } from 'rxjs';pour importer la fonction. De plus, forkJoin n'est plus membre d'Observable, mais une fonction distincte. Donc au lieu de Observable.forkJoin()c'est juste forkJoin().
Bas