Об’єднання спостережуваних даних 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');
    }   
}

Тепер у Component я хочу запустити (або ланцюг) обидва 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? Чому б не перемкнути карту? Я б припустив, що якщо початковий запит GET раптом виведе інше значення для Користувача, ви не хочете продовжувати отримувати зображення для попереднього значення Користувача
f.khantsis

4
Для тих, хто дивиться на це і дивується, чому це не працює: у RxJS 6 синтаксис import та forkJoin дещо змінився. Тепер вам потрібно додати, import { forkJoin } from 'rxjs';щоб імпортувати функцію. Крім того, forkJoin більше не є членом Observable, а є окремою функцією. Тож замість Observable.forkJoin()цього просто forkJoin().
Бас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.