การสร้างและส่งคืน Observable จาก Angular 2 Service


136

นี่เป็นคำถาม "แนวทางปฏิบัติที่ดีที่สุด" มากกว่า มีผู้เล่นสามคน: a Component, Serviceและ a Model. ComponentถูกเรียกServiceจะได้รับข้อมูลจากฐานข้อมูล Serviceใช้:

this.people = http.get('api/people.json').map(res => res.json());

เพื่อส่งคืนObservableไฟล์.

Componentก็สามารถสมัครเป็นสมาชิกObservable:

    peopleService.people
        .subscribe(people => this.people = people);
      }

อย่างไรก็ตามสิ่งที่ฉันต้องการจริงๆคือการServiceส่งคืนArray of Modelวัตถุที่สร้างขึ้นจากข้อมูลที่Serviceดึงมาจากฐานข้อมูล ฉันรู้ว่าComponentสามารถสร้างอาร์เรย์นี้ในวิธีสมัครสมาชิกได้ แต่ฉันคิดว่ามันจะสะอาดกว่าถ้าบริการทำเช่นนั้นและทำให้พร้อมใช้งานสำหรับไฟล์Component.

จะServiceสร้างObservableอาร์เรย์ใหม่ที่มีอาร์เรย์นั้นและส่งคืนได้อย่างไร

คำตอบ:


160

อัปเดต: 9/24/16 Angular 2.0 Stable

คำถามนี้ยังคงได้รับการเข้าชมจำนวนมากดังนั้นฉันจึงต้องการอัปเดต ด้วยความบ้าคลั่งของการเปลี่ยนแปลงจากผู้สมัคร Alpha, Beta และ 7 RC ฉันจึงหยุดอัปเดตคำตอบ SO ของฉันจนกว่าจะมีเสถียรภาพ

นี่เป็นกรณีที่สมบูรณ์แบบสำหรับการใช้SubjectsและReplaySubjects

ฉันชอบใช้เป็นการส่วนตัวReplaySubject(1)เนื่องจากอนุญาตให้ส่งค่าที่เก็บไว้ล่าสุดเมื่อสมาชิกใหม่แนบแม้ว่าจะสาย

let project = new ReplaySubject(1);

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //push onto subject
    project.next(result));

    //add delayed subscription AFTER loaded
    setTimeout(()=> project.subscribe(result => console.log('Delayed Stream:', result)), 3000);
});

//Output
//Subscription Streaming: 1234
//*After load and delay*
//Delayed Stream: 1234

ดังนั้นแม้ว่าฉันจะแนบสายหรือต้องโหลดในภายหลังฉันก็สามารถรับสายล่าสุดได้ตลอดเวลาและไม่ต้องกังวลว่าจะพลาดการโทรกลับ

นอกจากนี้ยังช่วยให้คุณใช้สตรีมเดียวกันเพื่อผลักดันไปยัง:

project.next(5678);
//output
//Subscription Streaming: 5678

แต่ถ้าคุณแน่ใจ 100% ว่าต้องโทรเพียงครั้งเดียว? การปล่อยให้ตัวแบบเปิดและสิ่งที่สังเกตได้นั้นไม่ดี แต่มักมีคำว่า"เกิดอะไรขึ้น"

นั่นคือจุดที่AsyncSubject เข้ามา

let project = new AsyncSubject();

//subscribe
project.subscribe(result => console.log('Subscription Streaming:', result),
                  err => console.log(err),
                  () => console.log('Completed'));

http.get('path/to/whatever/projects/1234').subscribe(result => {
    //push onto subject and complete
    project.next(result));
    project.complete();

    //add a subscription even though completed
    setTimeout(() => project.subscribe(project => console.log('Delayed Sub:', project)), 2000);
});

//Output
//Subscription Streaming: 1234
//Completed
//*After delay and completed*
//Delayed Sub: 1234

สุดยอด! แม้ว่าเราจะปิดหัวข้อ แต่ก็ยังตอบกลับด้วยสิ่งสุดท้ายที่โหลด

อีกประการหนึ่งคือเราสมัครรับการโทร http และจัดการการตอบกลับอย่างไร แผนที่เหมาะอย่างยิ่งในการประมวลผลการตอบกลับ

public call = http.get(whatever).map(res => res.json())

แต่ถ้าเราต้องการซ้อนสายเหล่านั้นล่ะ? ใช่คุณสามารถใช้วัตถุด้วยฟังก์ชันพิเศษ:

getThing() {
    resultSubject = new ReplaySubject(1);

    http.get('path').subscribe(result1 => {
        http.get('other/path/' + result1).get.subscribe(response2 => {
            http.get('another/' + response2).subscribe(res3 => resultSubject.next(res3))
        })
    })
    return resultSubject;
}
var myThing = getThing();

แต่นั่นเป็นจำนวนมากและหมายความว่าคุณต้องมีฟังก์ชันในการทำ เข้าสู่FlatMap :

var myThing = http.get('path').flatMap(result1 => 
                    http.get('other/' + result1).flatMap(response2 => 
                        http.get('another/' + response2)));

หวานvarเป็นสิ่งที่สังเกตได้ซึ่งรับข้อมูลจากการโทร http ครั้งสุดท้าย

โอเคดีมาก แต่ฉันต้องการบริการ angular2!

ฉันมีคุณ:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { ReplaySubject } from 'rxjs';

@Injectable()
export class ProjectService {

  public activeProject:ReplaySubject<any> = new ReplaySubject(1);

  constructor(private http: Http) {}

  //load the project
  public load(projectId) {
    console.log('Loading Project:' + projectId, Date.now());
    this.http.get('/projects/' + projectId).subscribe(res => this.activeProject.next(res));
    return this.activeProject;
  }

 }

 //component

@Component({
    selector: 'nav',
    template: `<div>{{project?.name}}<a (click)="load('1234')">Load 1234</a></div>`
})
 export class navComponent implements OnInit {
    public project:any;

    constructor(private projectService:ProjectService) {}

    ngOnInit() {
        this.projectService.activeProject.subscribe(active => this.project = active);
    }

    public load(projectId:string) {
        this.projectService.load(projectId);
    }

 }

ฉันเป็นแฟนตัวยงของผู้สังเกตการณ์และผู้สังเกตการณ์ดังนั้นฉันหวังว่าการอัปเดตนี้จะช่วยได้!

คำตอบเดิม

ฉันคิดว่านี่เป็นกรณีการใช้งานของการใช้วัตถุที่สังเกตได้หรือในAngular2ไฟล์EventEmitter.

ในบริการของคุณคุณสร้างสิ่งEventEmitterที่ช่วยให้คุณสามารถผลักดันค่าต่างๆ ในAlpha 45คุณต้องแปลงด้วยtoRx()แต่ฉันรู้ว่าพวกเขากำลังดำเนินการเพื่อกำจัดสิ่งนั้นดังนั้นในAlpha 46คุณอาจสามารถส่งคืนไฟล์EvenEmitter.

class EventService {
  _emitter: EventEmitter = new EventEmitter();
  rxEmitter: any;
  constructor() {
    this.rxEmitter = this._emitter.toRx();
  }
  doSomething(data){
    this.rxEmitter.next(data);
  }
}

วิธีนี้มีสิ่งเดียวEventEmitterที่ฟังก์ชั่นบริการต่างๆของคุณสามารถผลักดันได้

หากคุณต้องการส่งคืนสิ่งที่สังเกตได้โดยตรงจากการโทรคุณสามารถทำสิ่งนี้ได้:

myHttpCall(path) {
    return Observable.create(observer => {
        http.get(path).map(res => res.json()).subscribe((result) => {
            //do something with result. 
            var newResultArray = mySpecialArrayFunction(result);
            observer.next(newResultArray);
            //call complete if you want to close this stream (like a promise)
            observer.complete();
        });
    });
}

ซึ่งจะช่วยให้คุณทำสิ่งนี้ได้ในส่วนประกอบ: peopleService.myHttpCall('path').subscribe(people => this.people = people);

และยุ่งกับผลลัพธ์จากการโทรในบริการของคุณ

ฉันชอบสร้างEventEmitterสตรีมด้วยตัวเองในกรณีที่ฉันต้องการเข้าถึงจากส่วนประกอบอื่น ๆ แต่ฉันเห็นทั้งสองวิธีในการทำงาน ...

นี่คือ Plunker ที่แสดงบริการพื้นฐานพร้อมตัวปล่อยเหตุการณ์: Plunkr


ฉันลองใช้วิธีนี้ แต่ได้รับ "ไม่สามารถใช้ 'ใหม่' กับนิพจน์ที่ไม่มีการเรียกหรือสร้างลายเซ็น" -error ใครมีความคิดว่าจะทำอย่างไร?
Spock

3
@Spock ข้อมูลจำเพาะดูเหมือนจะอัปเดตเนื่องจากคำถามเดิมนี้ คุณไม่จำเป็นต้องมี "ใหม่" สำหรับสิ่งที่สังเกตได้อีกต่อไปเพราะมันทำเพื่อคุณ เพียงแค่ลบใหม่และแจ้งให้เราทราบว่าเกิดอะไรขึ้น ตอนนี้ฉันกำลังยุ่งกับบางสิ่งอยู่ถ้ามันเหมาะกับคุณฉันจะอัปเดตคำตอบนี้
Dennis Smolek

1
ใช้EventEmitterเพื่ออะไรก็ได้ แต่@Output()หมดกำลังใจ ดูstackoverflow.com/questions/34376854/…
GünterZöchbauer

@ GünterZöchbauerใช่แล้วตอนนี้ ... ในเวลานั้นมันจะเป็น EventEmitters ทั่ว แต่พวกเขาได้สร้างมาตรฐานบน Rx Observables แล้ว ตัวอย่างที่สังเกตได้ของฉันยังคงใช้งานได้ แต่ถ้าคุณจะใช้ตัวอย่าง EventEmitter ที่ฉันให้ฉันแนะนำให้ใช้ Subjects โดยตรง: github.com/Reactive-Extensions/RxJS/blob/master/doc/api/…
Dennis Smolek

1
@maxisam ขอบคุณสำหรับการแก้ไขแม้ว่าคำตอบจะ / สัมพันธ์กับการลบ Alpha "new" สำหรับ Observable นั้นถูกต้องแล้ว
Dennis Smolek

30

นี่คือตัวอย่างจากเอกสาร Angular2เกี่ยวกับวิธีสร้างและใช้ Observables ของคุณเอง:

บริการ

import {Injectable} from 'angular2/core'
import {Subject}    from 'rxjs/Subject';
@Injectable()
export class MissionService {
  private _missionAnnouncedSource = new Subject<string>();
  missionAnnounced$ = this._missionAnnouncedSource.asObservable();

  announceMission(mission: string) {
    this._missionAnnouncedSource.next(mission)
  }
}

ส่วนประกอบ

    import {Component}          from 'angular2/core';
    import {MissionService}     from './mission.service';

    export class MissionControlComponent {
      mission: string;

      constructor(private missionService: MissionService) {

        missionService.missionAnnounced$.subscribe(
          mission => {
            this.mission = mission;
          })
      }

      announce() {
        this.missionService.announceMission('some mission name');
      }
    }

ตัวอย่างเต็มรูปแบบและการทำงานสามารถพบได้ที่นี่: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service


18

ฉันต้องการเพิ่มว่าหากวัตถุที่สร้างขึ้นเป็นแบบคงที่และไม่ผ่าน http สิ่งนั้นสามารถทำได้:

public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return Observable.of(new TestModel()).map(o => JSON.stringify(o));
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .map(res => res.text());
      }
    }

แก้ไข: สำหรับการแมป Angular 7.xx ต้องทำโดยใช้ pipe () ตามที่อธิบายไว้ที่นี่ ( https://stackoverflow.com/a/54085359/986160 ):

import {of,  Observable } from 'rxjs';
import { map } from 'rxjs/operators';
[...]
public fetchModel(uuid: string = undefined): Observable<string> {
      if(!uuid) { //static data
        return of(new TestModel());
      }
      else {
        return this.http.get("http://localhost:8080/myapp/api/model/" + uuid)
                .pipe(map((res:any) => res)) //already contains json
      }
    }

จากคำตอบสำหรับคำถามของฉันเกี่ยวกับผู้สังเกตการณ์และข้อมูลคงที่: https://stackoverflow.com/a/35219772/986160


17

ฉันไปงานปาร์ตี้ช้าไปหน่อย แต่ฉันคิดว่าแนวทางของฉันมีข้อดีคือไม่มีการใช้ EventEmitters และ Subjects

นี่คือแนวทางของฉัน เราไม่สามารถไปจาก subscribe () ได้และเราไม่ต้องการ ในหลอดเลือดดำนั้นบริการของเราจะส่งคืนให้Observable<T>กับผู้สังเกตการณ์ที่มีสินค้าล้ำค่าของเรา จากผู้ที่โทรมาเราจะเริ่มต้นตัวแปร, และมันจะได้รับการบริการของObservable<T> Observable<T>ต่อไปเราจะสมัครรับข้อมูลวัตถุนี้ ในที่สุดคุณจะได้ "T" ของคุณ! จากบริการของคุณ

อันดับแรกบริการคนของเรา แต่ของคุณไม่ผ่านพารามิเตอร์นั่นเป็นจริงมากกว่า:

people(hairColor: string): Observable<People> {
   this.url = "api/" + hairColor + "/people.json";

   return Observable.create(observer => {
      http.get(this.url)
          .map(res => res.json())
          .subscribe((data) => {
             this._people = data

             observer.next(this._people);
             observer.complete();


          });
   });
}

อย่างที่คุณเห็นเราส่งคืนObservable"คน" ประเภทหนึ่ง ลายเซ็นของวิธีการพูดอย่างนั้น! เราเก็บ_peopleวัตถุไว้ในผู้สังเกตการณ์ของเรา เราจะเข้าถึงประเภทนี้จากผู้โทรของเราในคอมโพเนนต์ถัดไป!

ในส่วนประกอบ:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

getPeople(hairColor:string) {
   this._peopleObservable = this.peopleService.people(hairColor);

   this._peopleObservable.subscribe((data) => {
      this.people = data;
   });
}

เราเริ่มต้น_peopleObservableโดยการคืนค่าObservable<people>จากPeopleServiceไฟล์. จากนั้นเราสมัครรับข้อมูลคุณสมบัตินี้ สุดท้ายเราตั้งค่าthis.peopleการpeopleตอบกลับdata ( ) ของเรา

การออกแบบบริการในรูปแบบนี้มีข้อได้เปรียบที่สำคัญอย่างหนึ่งที่เหนือกว่าบริการทั่วไป: แผนที่ (... ) และส่วนประกอบ: รูปแบบ "สมัครสมาชิก (... )" ในโลกแห่งความเป็นจริงเราต้องแมป json กับคุณสมบัติของเราในคลาสของเราและบางครั้งเราก็ทำบางอย่างที่กำหนดเองที่นั่น ดังนั้นการทำแผนที่นี้สามารถเกิดขึ้นได้ในบริการของเรา และโดยทั่วไปเนื่องจากการเรียกใช้บริการของเราจะไม่ถูกใช้เพียงครั้งเดียว แต่ในที่อื่น ๆ ในโค้ดของเราเราไม่จำเป็นต้องทำการแมปนั้นในบางส่วนอีก ยิ่งไปกว่านั้นจะเป็นอย่างไรหากเราเพิ่มฟิลด์ใหม่ให้กับผู้คน ....


ฉันยอมรับว่าการจัดรูปแบบควรอยู่ในบริการและฉันโพสต์วิธีสังเกตมาตรฐานด้วย แต่ข้อดีของ Subjects ในบริการคือฟังก์ชันอื่น ๆ สามารถเรียกใช้งานได้ หากคุณต้องการเพียงการเรียก http โดยตรงเสมอไปฉันจะใช้วิธี Observable ..
Dennis Smolek

11

ในไฟล์ service.ts -

ก. นำเข้า 'ของ' จากที่สังเกตได้ / ของ
b สร้างรายการ json
c. ส่งคืนวัตถุ json โดยใช้ Observable.of ()
Ex. -

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

@Injectable()
export class ClientListService {
    private clientList;

    constructor() {
        this.clientList = [
            {name: 'abc', address: 'Railpar'},
            {name: 'def', address: 'Railpar 2'},
            {name: 'ghi', address: 'Panagarh'},
            {name: 'jkl', address: 'Panagarh 2'},
        ];
    }

    getClientList () {
        return Observable.of(this.clientList);
    }
};

ในส่วนประกอบที่เราเรียกใช้ฟังก์ชัน get ของบริการ -

this.clientListService.getClientList().subscribe(res => this.clientList = res);

ทำได้ดี @Anirban ยังสามารถกลับมาได้เฉพาะ (this.clientList);
foo-baar

7

สังเกตว่าคุณกำลังใช้Observable # mapเพื่อแปลงResponseออบเจ็กต์ดิบที่Observable ของคุณส่งออกเป็นการแสดงการตอบสนอง JSON แบบแยกวิเคราะห์

ถ้าฉันเข้าใจคุณถูกต้องคุณต้องการmapอีกครั้ง แต่คราวนี้การแปลง JSON ดิบนั้นเป็นอินสแตนซ์ของModelไฟล์. ดังนั้นคุณจะทำสิ่งที่ชอบ:

http.get('api/people.json')
  .map(res => res.json())
  .map(peopleData => peopleData.map(personData => new Person(personData)))

ดังนั้นคุณเริ่มต้นด้วย Observable ที่ปล่อยResponseวัตถุเปลี่ยนเป็นสิ่งที่สังเกตได้ซึ่งปล่อยวัตถุของ JSON ที่แยกวิเคราะห์ของการตอบสนองนั้นจากนั้นเปลี่ยนให้เป็นสิ่งที่สังเกตได้ซึ่งเปลี่ยน JSON ดิบให้เป็นอาร์เรย์ของโมเดลของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.