在Angular2中使用TypeScript从http数据链式调用RxJS Observables

103

我目前正在尝试自学Angular2和TypeScript,之前4年一直愉快地使用AngularJS 1.*!我不得不承认我讨厌它,但我相信我的顿悟时刻即将到来...无论如何,我在我的虚拟应用程序中编写了一个服务,可以从我编写的虚拟后端获取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),但是我的错误控制台疯狂地输出错误信息!

现在我已经尝试在我的组件中进行类似的操作(用.subscribe替换.then),但是我的错误控制台却疯狂输出错误信息!

@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标头时,我的函数中有重复的代码...

2个回答

153

针对您的使用情况,我认为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];
});

3
哦!我的答案中有一个错别字:“this.userData.getUserPhotos(), this.userData.getUserInfo()”应该是“this.userData.getUserPhotos, this.userData.getUserInfo()”。对不起! - Thierry Templier
1
为什么要使用flatMap?为什么不用switchMap?我认为,如果初始的GET请求突然输出了另一个User值,您就不会想继续获取先前User的图像。 - f.khantsis
如果我无法在其他函数中访问this.userPhotos以对其进行过滤和存储到其他变量中,那么这个this.userPhotos = data的用途是什么? - Janatbek Orozaly
4
对于任何想知道为什么它不起作用的人:在 RxJS 6 中,导入和 forkJoin 语法已经略有改变。现在您需要添加 import { forkJoin } from 'rxjs'; 来导入该函数。另外,forkJoin 不再是 Observable 的成员,而是一个独立的函数。因此,不再使用 Observable.forkJoin(),而是使用 forkJoin() - Bas
1
我很不爽它看起来并不是很规范,我一直在试图寻找一种不同的解决方案。更明确地说:你有函数getUserPhotos()。在其中调用了getUserInfo()。然后要么改名第一个函数为getUserPhotosAndAlsoGetUserInfo(),要么可能有另一种方法。因为对我来说,getUserInfo似乎不应该在getUserPhotos里面。在AngularJS中,使用promises很清楚,getUserInfo.then(getUserPhotos)。这里感觉就像它们全都混在一个大碗里... - bokkie
显示剩余7条评论

1

您实际需要的是 switchMap 操作符。它接收初始数据流(用户信息),当完成时,将其替换为图片可观察流。

以下是我对您的流程的理解:

  • 获取用户信息
  • 从该信息中获取用户ID
  • 使用用户ID获取用户照片

这里有一个演示。注意:我模拟了服务,但代码将与真实服务一起工作。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接