observables - Crear y devolver Observable desde el Servicio Angular 2
observables angular 5 (6)
En el archivo service.ts:
a.
importar ''de'' desde observable / de
si.
crear una lista json
C.
devolver el objeto 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);
}
};
En el componente donde estamos llamando a la función get del servicio:
this.clientListService.getClientList().subscribe(res => this.clientList = res);
Esta es más una pregunta de "mejores prácticas".
Hay tres jugadores: un
Component
, un
Service
y un
Model
.
El
Component
está llamando al
Service
para obtener datos de una base de datos.
El
Service
está utilizando:
this.people = http.get(''api/people.json'').map(res => res.json());
para devolver un
Observable
.
El
Component
podría suscribirse al
Observable
:
peopleService.people
.subscribe(people => this.people = people);
}
Sin embargo, lo que realmente quiero es que el
Service
devuelva una
Array of Model
objetos
Array of Model
que se creó a partir de los datos que el
Service
recuperó de la base de datos.
Me di cuenta de que el
Component
podría crear esta matriz en el método de suscripción, pero creo que sería más limpio si el servicio lo hace y lo pone a disposición del
Component
.
¿Cómo puede el
Service
crear un nuevo
Observable
que contenga esa matriz y devolverlo?
Este es un ejemplo de los documentos de Angular2 sobre cómo puede crear y usar sus propios Observables:
El servicio
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)
}
}
El 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'');
}
}
Puede encontrar un ejemplo completo y funcional aquí: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Llego un poco tarde a la fiesta, pero creo que mi enfoque tiene la ventaja de que carece del uso de EventEmitters y Asignaturas.
Entonces, aquí está mi enfoque.
No podemos escapar de subscribe (), y no queremos hacerlo.
En ese sentido, nuestro servicio devolverá un
Observable<T>
con un observador que tiene nuestra preciosa carga.
Desde la persona que llama, inicializaremos una variable,
Observable<T>
, y obtendrá el
Observable<T>
.
A continuación, nos suscribiremos a este objeto.
Finalmente, obtienes tu "T"!
de su servicio
Primero, nuestro servicio de personas, pero el suyo no pasa parámetros, eso es más realista:
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();
});
});
}
Ok, como pueden ver, estamos devolviendo un
Observable
del tipo "personas".
La firma del método, incluso lo dice!
_people
objeto
_people
en nuestro observador.
A continuación, accederemos a este tipo desde nuestra persona que llama en el Componente.
En el componente:
private _peopleObservable: Observable<people>;
constructor(private peopleService: PeopleService){}
getPeople(hairColor:string) {
this._peopleObservable = this.peopleService.people(hairColor);
this._peopleObservable.subscribe((data) => {
this.people = data;
});
}
Inicializamos nuestro
_peopleObservable
devolviendo ese
Observable<people>
de nuestro
PeopleService
.
Luego, nos suscribimos a esta propiedad.
Finalmente, configuramos
this.people
a nuestra respuesta de datos (
people
).
Diseñar el servicio de esta manera tiene una gran ventaja sobre el servicio típico: mapa (...) y componente: patrón "suscribirse (...)". En el mundo real, necesitamos asignar el json a nuestras propiedades en nuestra clase y, a veces, hacemos algunas cosas personalizadas allí. Entonces este mapeo puede ocurrir en nuestro servicio. Y, típicamente, debido a que nuestra llamada de servicio se usará no una vez, sino, probablemente, en otros lugares de nuestro código, no tenemos que realizar esa asignación en algún componente, nuevamente. Además, ¿qué pasa si agregamos un nuevo campo a las personas? ...
Me gustaría agregar que si el objeto que se crea es estático y no llega a través de http, se puede hacer algo así:
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());
}
}
Editar: para el mapeo Angular 7.xx se debe hacer usando pipe () como se describe aquí ( https://.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
}
}
de la respuesta a mi pregunta sobre observadores y datos estáticos: https://.com/a/35219772/986160
Observe que está utilizando
Map
para convertir el objeto de
Response
sin
Response
que su Observable base emite en una representación analizada de la respuesta JSON.
Si te entendí correctamente, quieres volver a
map
.
Pero esta vez, convirtiendo ese JSON sin procesar en instancias de su
Model
.
Entonces harías algo como:
http.get(''api/people.json'')
.map(res => res.json())
.map(peopleData => peopleData.map(personData => new Person(personData)))
Entonces, comenzaste con un Observable que emite un objeto de
Response
, lo convertiste en un observable que emite un objeto del JSON analizado de esa respuesta, y luego lo convertiste en otro observable que convirtió ese JSON sin procesar en una matriz de tus modelos.
ACTUALIZACIÓN: 9/24/16 Angular 2.0 Estable
Esta pregunta todavía tiene mucho tráfico, así que quería actualizarla. Con la locura de los cambios de Alpha, Beta y 7 candidatos RC, dejé de actualizar mis respuestas SO hasta que se estabilizaron.
Este es el caso perfecto para usar Subjects y ReplaySubjects
Personalmente,
prefiero usar
ReplaySubject(1)
ya que permite que se pase el último valor almacenado cuando se conectan nuevos suscriptores, incluso cuando llega tarde:
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
Entonces, incluso si adjunto tarde o necesito cargar más tarde, siempre puedo recibir la última llamada y no preocuparme por perder la devolución de llamada.
Esto también le permite usar la misma secuencia para presionar hacia abajo:
project.next(5678);
//output
//Subscription Streaming: 5678
Pero, ¿qué sucede si está 100% seguro de que solo necesita hacer la llamada una vez? Dejar temas abiertos y observables no es bueno, pero siempre está ese "¿Qué pasa si?"
Ahí es donde entra 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
¡Increíble! Aunque cerramos el tema, todavía respondió con lo último que cargó.
Otra cosa es cómo nos suscribimos a esa llamada http y manejamos la respuesta. Map es excelente para procesar la respuesta.
public call = http.get(whatever).map(res => res.json())
Pero, ¿y si necesitáramos anidar esas llamadas? Sí, podría usar sujetos con una función especial:
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();
Pero eso es mucho y significa que necesita una función para hacerlo. Ingrese FlatMap :
var myThing = http.get(''path'').flatMap(result1 =>
http.get(''other/'' + result1).flatMap(response2 =>
http.get(''another/'' + response2)));
Dulce, el
var
es un observable que obtiene los datos de la llamada http final.
OK, eso es genial, pero quiero un servicio angular2.
Te tengo:
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);
}
}
Soy un gran admirador de los observadores y observables, ¡así que espero que esta actualización ayude!
Respuesta original
Creo que este es un caso de uso del uso de un
Subjects
o en
Angular2
el
EventEmitter
.
En su servicio, crea un
EventEmitter
que le permite insertar valores en él.
En
Alpha 45
, debe convertirlo con
toRx()
, pero sé que estaban trabajando para deshacerse de eso, por lo que en
Alpha 46
puede simplemente devolver el
EvenEmitter
.
class EventService {
_emitter: EventEmitter = new EventEmitter();
rxEmitter: any;
constructor() {
this.rxEmitter = this._emitter.toRx();
}
doSomething(data){
this.rxEmitter.next(data);
}
}
De esta manera tiene el único
EventEmitter
que ahora pueden empujar sus diferentes funciones de servicio.
Si desea devolver un observable directamente de una llamada, puede hacer algo como esto:
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();
});
});
}
Eso le permitiría hacer esto en el componente:
peopleService.myHttpCall(''path'').subscribe(people => this.people = people);
Y meterse con los resultados de la llamada en su servicio.
Me gusta crear la secuencia
EventEmitter
por sí sola en caso de que necesite acceder a ella desde otros componentes, pero pude ver que ambas formas funcionan ...
Aquí hay un plunker que muestra un servicio básico con un emisor de eventos: Plunkr