การเชื่อมโยง RxJS Observables จากข้อมูล http ใน Angular2 ด้วย TypeScript


99

ฉันกำลังพยายามสอนตัวเอง 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 สิ่งนี้ง่ายเหมือนในคอนโทรลเลอร์ของฉันฉันจะทำอะไรแบบนี้เพื่อหลีกเลี่ยง "Pyramid of doom" ...

// 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 ของฉัน ...

คำตอบ:


141

สำหรับกรณีการใช้งานของคุณฉันคิดว่าตัว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]'
Mike Sav

คุณมีข้อผิดพลาดนี้ที่ไหน เมื่อโทรthis.userData.getUserInfo()?
Thierry Templier

3
โอ้! มีการพิมพ์ผิดในคำตอบของฉันคือ: แทนthis.userData.getUserPhotos(), this.userData.getUserInfo() this.userData.getUserPhotos, this.userData.getUserInfo()ขออภัย!
Thierry Templier

1
ทำไมต้อง flatMap? ทำไมไม่เปลี่ยนแผนที่? ฉันคิดว่าถ้าคำขอ GET เริ่มต้นส่งออกค่าอื่นให้กับผู้ใช้ในทันทีคุณคงไม่ต้องการรับภาพสำหรับค่าก่อนหน้าของ User
f.khantsis

4
สำหรับใครก็ตามที่ดูสิ่งนี้และสงสัยว่าทำไมมันถึงไม่ทำงาน: ใน RxJS 6 ไวยากรณ์การนำเข้าและ forkJoin มีการเปลี่ยนแปลงเล็กน้อย ตอนนี้คุณต้องเพิ่มimport { forkJoin } from 'rxjs';เพื่อนำเข้าฟังก์ชัน นอกจากนี้ forkJoin ไม่ได้เป็นสมาชิกของ Observable อีกต่อไป แต่เป็นฟังก์ชันแยกต่างหาก ดังนั้นแทนที่จะเป็นเพียงObservable.forkJoin() forkJoin()
Bas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.