Membuat dan mengembalikan Dapat Diamati dari Layanan Angular 2

132

Ini lebih merupakan pertanyaan "praktik terbaik". Ada tiga pemain: a Component, a Servicedan a Model. The Componentadalah memanggil Serviceuntuk mendapatkan data dari database. The Serviceadalah menggunakan:

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

untuk mengembalikan Observable.

The Componenthanya bisa berlangganan Observable:

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

Namun, apa yang saya inginkan adalah Servicemengembalikan Array of Modelobjek yang dibuat dari data yang Servicediambil dari database. Saya menyadari bahwa Componenthanya bisa membuat array ini dalam metode berlangganan, tapi saya pikir akan lebih bersih jika layanan melakukan itu dan membuatnya tersedia untuk Component.

Bagaimana cara Servicemembuat yang baru Observable, yang berisi array itu, dan mengembalikannya?

Joseph Genchik
sumber

Jawaban:

159

UPDATE: 9/24/16 Angular 2.0 Stable

Pertanyaan ini masih mendapatkan banyak lalu lintas, jadi, saya ingin memperbaruinya. Dengan kegilaan perubahan dari kandidat Alpha, Beta, dan 7 RC, saya berhenti memperbarui jawaban SO saya sampai mereka menjadi stabil.

Ini adalah kasus yang sempurna untuk menggunakan Subjek dan ReplaySubyek

Saya pribadi lebih suka menggunakan ReplaySubject(1)karena memungkinkan nilai terakhir yang disimpan diteruskan ketika pelanggan baru melampirkan bahkan ketika terlambat:

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

Jadi, bahkan jika saya melampirkan terlambat atau perlu memuat nanti saya selalu bisa mendapatkan panggilan terbaru dan tidak khawatir kehilangan callback.

Ini juga memungkinkan Anda menggunakan aliran yang sama untuk mendorong ke:

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

Tetapi bagaimana jika Anda yakin 100%, bahwa Anda hanya perlu melakukan panggilan sekali saja? Meninggalkan subjek terbuka dan dapat diobservasi tidak baik tetapi selalu ada "Bagaimana Jika?"

Di situlah AsyncSubject masuk.

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

Luar biasa! Meskipun kami menutup subjek, tetap menjawab dengan hal terakhir yang dimuat.

Hal lain adalah bagaimana kami berlangganan panggilan http itu dan menangani responsnya. Peta bagus untuk memproses respons.

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

Tetapi bagaimana jika kita perlu membuat sarang panggilan itu? Ya, Anda dapat menggunakan subjek dengan fungsi khusus:

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

Tapi itu banyak dan berarti Anda perlu fungsi untuk melakukannya. Masukkan FlatMap :

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

Manis, varini bisa diamati yang mendapatkan data dari panggilan http akhir.

OK itu bagus tapi saya ingin layanan angular2!

Aku mendapatkanmu:

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

 }

Saya penggemar berat pengamat dan pengamat jadi saya harap pembaruan ini membantu!

Jawaban Asli

Saya rasa ini adalah kasus penggunaan menggunakan Subjek diamati atau Angular2yang EventEmitter.

Dalam layanan Anda, Anda membuat EventEmitteryang memungkinkan Anda untuk mendorong nilai ke dalamnya. Di Alpha 45 Anda harus mengonversinya dengan toRx(), tetapi saya tahu mereka sedang berusaha untuk menghilangkannya, jadi di Alpha 46 Anda mungkin dapat mengembalikannya EvenEmitter.

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

Cara ini memiliki satu EventEmitteryang dapat didorong oleh fungsi layanan Anda yang berbeda.

Jika Anda ingin mengembalikan observable langsung dari panggilan Anda bisa melakukan sesuatu seperti ini:

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

Itu akan memungkinkan Anda melakukan ini di komponen: peopleService.myHttpCall('path').subscribe(people => this.people = people);

Dan mengacaukan hasil dari panggilan di layanan Anda.

Saya suka membuat EventEmitteraliran sendiri jika saya perlu mendapatkan akses dari komponen lain, tetapi saya bisa melihat kedua cara bekerja ...

Berikut adalah plunker yang menampilkan layanan dasar dengan emitor acara: Plunkr

Dennis Smolek
sumber
Saya mencoba pendekatan ini tetapi mendapat "Tidak dapat menggunakan 'baru' dengan ekspresi yang tipenya tidak memiliki panggilan atau membangun tanda tangan" -teror. Adakah yang tahu apa yang harus dilakukan?
Spock
3
@Spock spek sepertinya telah diperbarui sejak pertanyaan asli ini. Anda tidak lagi membutuhkan "baru" untuk dapat diamati seperti halnya ini dilakukan untuk Anda. Cukup hapus yang baru dan beri tahu saya apa yang terjadi. Saya mengacaukan beberapa hal sekarang, jika berhasil juga untuk Anda, saya akan memperbarui jawaban ini
Dennis Smolek
1
Menggunakan EventEmitteruntuk apa pun tetapi @Output()tidak dianjurkan. Lihat juga stackoverflow.com/questions/34376854/…
Günter Zöchbauer
@ GünterZöchbauer, Ya itu sekarang ... Pada saat itu akan menjadi EventEmitters di seluruh tetapi mereka sudah sejak standar pada Rx Observables. Contoh yang dapat diobservasi saya masih berfungsi tetapi jika Anda akan menggunakan contoh EventEmitter yang saya berikan, saya sarankan untuk menggunakan Subjek secara langsung: github.com/Reactive-Extensions/RxJS/blob/master/doc/api/…
Dennis Smolek
1
@maxisam Terima kasih atas hasil editnya, walaupun jawabannya adalah / relatif terhadap Alpha yang menghapus "baru" untuk Observable sudah benar sekarang
Dennis Smolek
29

Ini adalah contoh dari dokumen Angular2 tentang bagaimana Anda dapat membuat dan menggunakan Observables Anda sendiri:

Layanan

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

Komponen

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

Contoh lengkap dan berfungsi dapat ditemukan di sini: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Tibbus
sumber
18

Saya ingin menambahkan bahwa jika objek yang dibuat adalah statis dan tidak datang melalui http sesuatu seperti itu dapat dilakukan:

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

Sunting: Untuk pemetaan 7.xx Angular perlu dilakukan dengan menggunakan pipa () seperti yang dijelaskan di sini ( 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
      }
    }

dari jawaban pertanyaan saya tentang pengamat dan data statis: https://stackoverflow.com/a/35219772/986160

Michail Michailidis
sumber
17

Saya sedikit terlambat ke pesta, tapi saya pikir pendekatan saya memiliki keuntungan karena kurang menggunakan EventEmitter dan Subjek.

Jadi, inilah pendekatan saya. Kami tidak dapat menghindar dari berlangganan (), dan kami tidak mau. Dalam nada itu, layanan kami akan mengembalikan sebuah Observable<T>dengan pengamat yang memiliki barang berharga kami. Dari pemanggil, kami akan menginisialisasi variabel Observable<T>,, dan itu akan mendapatkan layanan Observable<T>. Selanjutnya, kami akan berlangganan objek ini. Akhirnya, Anda mendapatkan "T" Anda! dari layanan Anda.

Pertama, layanan orang kami, tetapi milik Anda tidak lulus parameter, itu lebih realistis:

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


          });
   });
}

Oke, seperti yang Anda lihat, kami mengembalikan Observablejenis "orang". Tanda tangan dari metode ini, bahkan mengatakan demikian! Kami memasukkan _peoplebenda itu ke pengamat kami. Kami akan mengakses jenis ini dari pemanggil kami di Komponen, selanjutnya!

Dalam Komponen:

private _peopleObservable: Observable<people>;

constructor(private peopleService: PeopleService){}

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

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

Kami menginisialisasi _peopleObservabledengan mengembalikan itu Observable<people>dari kami PeopleService. Kemudian, kami berlangganan properti ini. Akhirnya, kami menetapkan respons this.peopledata ( people) kami.

Merancang layanan dengan cara ini memiliki satu keunggulan utama dibandingkan layanan khas: map (...) dan komponen: pola "berlangganan (...)". Di dunia nyata, kita perlu memetakan json ke properti kita di kelas kita dan, kadang-kadang, kita melakukan beberapa hal khusus di sana. Jadi pemetaan ini dapat terjadi di layanan kami. Dan, biasanya, karena panggilan layanan kami akan digunakan tidak hanya sekali, tetapi, mungkin, di tempat lain dalam kode kami, kami tidak perlu melakukan pemetaan itu di beberapa komponen, lagi. Apalagi, bagaimana jika kita menambahkan bidang baru ke orang-orang? ....

LargeDachshund
sumber
Saya setuju bahwa pemformatan harus dalam layanan dan saya memposting metode standar yang dapat diamati juga, tetapi keuntungan dari Subjek dalam layanan adalah bahwa fungsi lain dapat memicu pada itu. Jika Anda selalu hanya membutuhkan panggilan http langsung maka saya akan menggunakan metode Observable ..
Dennis Smolek
9

Dalam file service.ts -

Sebuah. impor 'dari' dari yang dapat diamati / dari
b. buat daftar json
c. mengembalikan objek json menggunakan 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);
    }
};

Dalam komponen di mana kita memanggil fungsi get dari layanan -

this.clientListService.getClientList().subscribe(res => this.clientList = res);
Anirban Bhadra
sumber
Kerja bagus @Anirban, bisa juga hanya mengembalikan (this.clientList);
foo-baar
7

Perhatikan bahwa Anda menggunakan peta # yang dapat diobservasi untuk mengonversi Responseobjek mentah yang dipancarkan oleh basis Anda menjadi representasi yang diuraikan dari respons JSON.

Jika saya mengerti Anda dengan benar, Anda ingin maplagi. Tapi kali ini, ubah JSON mentah itu menjadi instance dari Anda Model. Jadi, Anda akan melakukan sesuatu seperti:

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

Jadi, Anda mulai dengan Observable yang memancarkan Responseobjek, mengubahnya menjadi observable yang memancarkan objek dari JSON yang diuraikan respon itu, dan kemudian mengubahnya menjadi lagi yang bisa diamati yang mengubah JSON mentah itu menjadi array model Anda.

julioolvr
sumber