Encadeando RxJS observáveis ​​de dados http em Angular2 com TypeScript

95

Atualmente, estou tentando aprender Angular2 e TypeScript depois de trabalhar com prazer com AngularJS 1. * nos últimos 4 anos! Tenho que admitir que estou odiando isso, mas tenho certeza de que meu momento eureka está chegando ... de qualquer maneira, escrevi um serviço em meu aplicativo fictício que buscará dados http de um back-end falso que escrevi que 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');
    }   
}

Agora, em um componente, desejo executar (ou encadear) os métodos getUserInfo()e getUserPhotos(myId). No AngularJS isso foi fácil, pois no meu controlador eu faria algo assim para evitar a "Pirâmide da desgraça" ...

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

Agora eu tentei fazer algo semelhante no meu componente (substituindo .thena .subscribe) no entanto o meu console de erro enlouquecendo!

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

}

Obviamente, estou fazendo algo errado ... como seria a melhor com Observables e RxJS? Desculpe se estou fazendo perguntas estúpidas ... mas obrigado pela ajuda desde já! Também notei o código repetido em minhas funções ao declarar meus cabeçalhos http ...

Mike Sav
fonte

Respostas:

137

Para o seu caso de uso, acho que o flatMapoperador é o que você precisa:

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

Dessa forma, você executará a segunda solicitação assim que a primeira for recebida. O flatMapoperador é particularmente útil quando você deseja usar o resultado da solicitação anterior (evento anterior) para executar outra. Não se esqueça de importar o operador para poder usá-lo:

import 'rxjs/add/operator/flatMap';

Esta resposta pode fornecer mais detalhes:

Se você quiser usar apenas o subscribemétodo, use algo assim:

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

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

Para finalizar, se você deseja executar ambas as solicitações em paralelo e ser notificado quando todos os resultados chegarem, você deve considerar o uso Observable.forkJoin(é necessário adicionar 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
fonte
No entanto, recebo o erro 'TypeError: source.subscribe não é uma função em [null]'
Mike Sav
Onde você tem esse erro? Ao ligar this.userData.getUserInfo()?
Thierry Templier
3
Oh! Houve um erro de digitação em minha resposta: em this.userData.getUserPhotos(), this.userData.getUserInfo()vez de this.userData.getUserPhotos, this.userData.getUserInfo(). Desculpe!
Thierry Templier
1
porque flatMap? Por que não switchMap? Eu presumiria que, se a solicitação GET inicial de repente gerar outro valor para User, você não gostaria de continuar recebendo imagens para o valor anterior de User
f.khantsis
4
Para quem está olhando para isso e se perguntando por que não está funcionando: no RxJS 6 a sintaxe import e forkJoin mudaram ligeiramente. Agora você precisa adicionar import { forkJoin } from 'rxjs';para importar a função. Além disso, forkJoin não é mais um membro do Observable, mas uma função separada. Então, ao invés de Observable.forkJoin()ser justo forkJoin().
Bas