Цепочка наблюдаемых RxJS из данных http в Angular2 с помощью TypeScript

96

В настоящее время я пытаюсь научиться Angular2 и TypeScript после того, как успешно работал с AngularJS 1. * в течение последних 4 лет! Я должен признать, что ненавижу это, но я уверен, что момент моей эврики не за горами ... в любом случае, я написал службу в своем фиктивном приложении, которое будет извлекать данные http из фальшивого бэкэнда, который я написал, который обслуживает 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');
    }   
}

Теперь в компоненте я хочу запустить (или цепь) , так getUserInfo()и getUserPhotos(myId)методы. В AngularJS это было просто, так как в моем контроллере я бы сделал что-то вроде этого, чтобы избежать «Пирамиды гибели» ...

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

Теперь я пытался сделать что - то подобное в моем компоненте (замена .thenдля .subscribe) , однако моя консоль ошибок сходит с ума!

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

}

Я явно делаю что-то не так ... как мне лучше всего использовать Observables и RxJS? Извините, если задаю глупые вопросы ... но заранее спасибо за помощь! Я также заметил повторяющийся код в моих функциях при объявлении заголовков http ...

Майк Сав
источник

Ответы:

138

Для вашего случая использования я думаю, что flatMapоператор - это то, что вам нужно:

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

Таким образом, вы выполните второй запрос после получения первого. flatMapОператор особенно полезен , когда вы хотите использовать результат предыдущего запроса (предыдущее событие) , чтобы выполнить еще одну. Не забудьте импортировать оператор, чтобы иметь возможность его использовать:

import 'rxjs/add/operator/flatMap';

Этот ответ может дать вам более подробную информацию:

Если вы хотите использовать только subscribeметод, используйте что-то вроде этого:

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

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

В заключение, если вы хотите выполнять оба запроса параллельно и получать уведомление, когда будут получены все результаты, вам следует подумать об использовании Observable.forkJoin(вам необходимо добавить import 'rxjs/add/observable/forkJoin'):

Observable.forkJoin([
  this.userData.getUserPhotos(),
  this.userData.getUserInfo()]).subscribe(t=> {
    var firstResult = t[0];
    var secondResult = t[1];
});
Тьерри Темплиер
источник
Однако я получаю сообщение об ошибке «TypeError: source.subscribe не является функцией в [null]»
Майк Сав
Где у вас эта ошибка? При звонке this.userData.getUserInfo()?
Тьерри Темплиер
3
Ой! В моем ответе была опечатка: this.userData.getUserPhotos(), this.userData.getUserInfo()вместо this.userData.getUserPhotos, this.userData.getUserInfo(). Сожалею!
Тьерри Темплиер
1
почему FlatMap? Почему бы не switchMap? Я бы предположил, что если первоначальный запрос GET внезапно выдаст другое значение для User, вы не захотите продолжать получать изображения для предыдущего значения User
f.khantsis
4
Для всех, кто смотрит на это и задается вопросом, почему это не работает: в RxJS 6 синтаксис импорта и forkJoin немного изменился. Теперь вам нужно добавить, import { forkJoin } from 'rxjs';чтобы импортировать функцию. Кроме того, forkJoin больше не является членом Observable, а является отдельной функцией. Так что вместо Observable.forkJoin()этого просто forkJoin().
Bas