Creazione e restituzione del servizio Osservabile da Angular 2

Questa è più una domanda sulle “migliori pratiche”. Ci sono tre giocatori: un Component , un Service e un Model . Il Component sta chiamando il Service per ottenere dati da un database. Il Service sta usando:

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

restituire un Observable .

La Component potrebbe semplicemente iscriversi Observable :

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

Tuttavia, ciò che desidero è che il Service restituisca un object Array of Model creato dai dati recuperati dal Service dal Service . Mi sono reso conto che Component poteva semplicemente creare questo array nel metodo subscribe, ma penso che sarebbe più pulito se il servizio lo facesse e lo rendesse disponibile al Component .

Come può il Service creare un nuovo Observable , contenente quell’array e restituirlo?

AGGIORNAMENTO: 8/24/16 Stabile angular 2.0

Questa domanda ha ancora molto traffico, quindi, volevo aggiornarla. Con la follia dei cambiamenti da Alpha, Beta e 7 candidati RC, ho smesso di aggiornare le mie risposte SO fino a quando non sono diventati stabili.

Questo è il caso perfetto per usare Soggetti e ReplaySubjects

Personalmente preferisco usare ReplaySubject(1) perché consente di passare l’ultimo valore memorizzato quando i nuovi abbonati si collegano anche quando in ritardo:

 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 

Quindi, anche se mi collego tardi o ho bisogno di caricare più tardi, posso sempre ricevere l’ultima chiamata e non preoccuparmi di perdere la richiamata.

Ciò consente anche di utilizzare lo stesso stream per eseguire il push su:

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

Ma cosa succede se sei sicuro al 100% che devi solo chiamare una volta? Lasciare soggetti aperti e osservabili non va bene, ma c’è sempre “What If?”

Ecco dove arriva 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 

Eccezionale! Anche se abbiamo chiuso l’argomento, ha comunque risposto con l’ultima cosa che ha caricato.

Un’altra cosa è come ci siamo iscritti a quella chiamata http e abbiamo gestito la risposta. La mappa è ottima per elaborare la risposta.

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

Ma se avessimo bisogno di annidare quelle chiamate? Sì, potresti usare i soggetti con una funzione speciale:

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

Ma questo è molto e significa che hai bisogno di una funzione per farlo. Inserisci FlatMap :

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

Dolce, il var è un osservabile che riceve i dati dalla chiamata http finale.

OK, va bene ma voglio un servizio angular2!

Ti ho preso:

 import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { ReplaySubject } from 'rxjs'; @Injectable() export class ProjectService { public activeProject:ReplaySubject = 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: `
{{project?.name}}Load 1234
` }) 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); } }

Sono un grande fan di osservatori e osservabili quindi spero che questo aggiornamento aiuti!

Risposta originale

Penso che questo sia un caso d’uso di usare un sobject osservabile o in Angular2 EventEmitter .

Nel tuo servizio crei un EventEmitter che ti permette di spingere i valori su di esso. In Alpha 45 devi convertirlo con toRx() , ma so che stavano lavorando per sbarazzartene, quindi in Alpha 46 potresti essere in grado di restituire semplicemente EvenEmitter .

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

In questo modo è presente il singolo EventEmitter che le diverse funzioni di servizio possono ora EventEmitter .

Se volessi restituire un osservabile direttamente da una chiamata, potresti fare qualcosa del genere:

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

Questo ti permetterebbe di farlo nel componente: peopleService.myHttpCall('path').subscribe(people => this.people = people);

E confusione con i risultati della chiamata nel tuo servizio.

Mi piace creare il stream EventEmitter da solo nel caso in cui ho bisogno di accedere ad esso da altri componenti, ma ho potuto vedere entrambi i modi di lavorare …

Ecco un plunker che mostra un servizio di base con un emettitore di eventi: Plunkr

Questo è un esempio dei documenti di Angular2 su come è ansible creare e utilizzare i propri Observables:

Il servizio

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

Il componente

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

L’esempio completo e funzionante può essere trovato qui: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Vorrei aggiungere che se l’object che viene creato è statico e non passa attraverso http, qualcosa come questo può essere fatto:

 public fetchModel(uuid: string = undefined): Observable { 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()); } } 

dalla risposta alla mia domanda su osservatori e dati statici: https://stackoverflow.com/a/35219772/986160

Sono un po ‘in ritardo per la festa, ma penso che il mio approccio abbia il vantaggio di non utilizzare EventEmitters e gli oggetti.

Quindi, ecco il mio approccio. Non possiamo scappare da subscribe (), e non vogliamo. In questo senso, il nostro servizio restituirà un Observable con un osservatore che ha il nostro prezioso carico. Dal chiamante, inizializzeremo una variabile, Observable , e otterrà Observable . Successivamente, ci iscriveremo a questo object. Finalmente, ottieni la tua “T”! dal tuo servizio.

Innanzitutto, il nostro servizio di persone, ma il tuo non passa i parametri, è più realistico:

 people(hairColor: string): Observable { 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(); }); }); } 

Ok, come puoi vedere, stiamo restituendo un Observable di tipo “persone”. La firma del metodo, lo dice anche così! _people object della _people nel nostro osservatore. Accederemo a questo tipo dal nostro chiamante nel componente, in seguito!

Nel componente:

 private _peopleObservable: Observable; constructor(private peopleService: PeopleService){} getPeople(hairColor:string) { this._peopleObservable = this.peopleService.people(hairColor); this._peopleObservable.subscribe((data) => { this.people = data; }); } 

Inizializziamo il nostro _peopleObservable restituendo _peopleObservable Observable dal nostro PeopleService . Quindi, ci iscriviamo a questa proprietà. Alla fine, impostiamo questa this.people alla nostra risposta di dati ( people ).

Architettare il servizio in questo modo ha uno, maggiore vantaggio rispetto al servizio tipico: mappa (…) e componente: schema “iscriviti (…)”. Nel mondo reale, abbiamo bisogno di mappare il json alle nostre proprietà nella nostra class e, a volte, facciamo qualcosa di personalizzato lì. Quindi questa mapping può verificarsi nel nostro servizio. E, in genere, poiché la nostra chiamata di servizio verrà utilizzata non una volta, ma, probabilmente, in altri punti del nostro codice, non sarà necessario eseguire nuovamente tale mapping in alcuni componenti. Inoltre, cosa succede se aggiungiamo un nuovo campo alle persone? ….

Si noti che si sta utilizzando la mappa # osservabile per convertire l’object Response base che emette l’osservabile di base in una rappresentazione analizzata della risposta JSON.

Se ti ho capito bene, vuoi map nuovo. Ma questa volta, convertendo quel JSON crudo in istanze del tuo Model . Quindi dovresti fare qualcosa come:

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

Quindi, hai iniziato con un Observable che emette un object Response , trasformandolo in un osservabile che emette un object del JSON analizzato di quella risposta, e poi lo ha trasformato in un altro osservabile che ha trasformato quel JSON crudo in un array dei tuoi modelli.

Nel file service.ts –

un. importazione ‘di’ da osservabile / di
b. creare una lista json
c. restituire object json usando 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); } }; 

Nel componente in cui stiamo chiamando la funzione get del servizio –

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