Búsqueda de sitios web

javascript - Cómo crear un observable dentro de un observable en Angular2


Podría estar equivocado sobre el proceso, pero aquí va:

Tengo un servicio de angular2. La fuente de los datos de este servicio va a ser localstorage... más tarde opcionalmente se actualizará cuando una llamada a la base de datos usando http regrese. Debido a que querré actualizar los datos devueltos a medida que las distintas fuentes regresen, parece que quiero usar observables. Por ahora, solo estoy tratando de comprender el concepto, así que he omitido el aspecto de localstorage... pero estoy incluyendo la 'historia de fondo' para que tenga (algo de) sentido por qué quiero hacer esto en múltiples métodos.

Mi idea era tener un método "getHTTPEvents()" que devolviera un observable con la carga útil siendo los eventos de la base de datos. (la teoría es que en algún momento en el futuro también tendría un método "getLSEvents()" que se aprovecharía allí)

Para simular eso, tengo este código:

private eventsUrl = 'app/mock-events.json';
getHTTPEvents() : Observable<Array<any>> { 
    return this._http.get(this.eventsUrl)
        .map(response => response.json()['events'])
        .catch(this.handleError); // handle error is a logging method
}

Mi objetivo sería crear un método que permita filtrar los eventos devueltos y, al mismo tiempo, siga devolviendo un observable a los usuarios del servicio. Ahí es donde está mi problema. Con ese objetivo, tengo un método público que será llamado por los usuarios del servicio. (intenté usar el patrón de aquí https://coryrylan.com/blog/angular-2-observable-data-services)

public getEvents(key:string,value:string) : Observable<Array<any>> {
    var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();
    var filteredEventsObserve : Observable<Array<any>>;


    allEventsObserve 
    .subscribe(
        events => {
            for(var i=0;i<events.length;i++) {
                if(events[i][key]==value) {
                    console.log('MATCH!!!' + events[i][key]); // THIS WORKS!
                    return new Observable(observer => filteredEventsObserve = observer);  // what do I need to return here?  I want to return an observable so the service consumer can get updates
                }
            }
            return allEventsObserve
        },
        error =>  console.error("Error retrieving all events for filtering: " + error));

}

Lo anterior no funciona. He visto muchos videos y leído muchos tutoriales sobre observables, pero nada de lo que encuentro parece profundizar más allá de crear y usar el observable http.

Además, intenté este método para crear el nuevo observable:

var newObs = Observable.create(function (observer) {
                    observer.next(events[i]);
                    observer.complete(events[i]);
                });

Y aunque al menos eso compila, no estoy seguro de cómo 'devolverlo' en el momento adecuado... ya que no puedo 'Crear' eso fuera del método allEventsObserve.subscribe (porque 'events' no existe) y no puedo (parece) 'devolverlo' desde dentro del subscribe. Tampoco estoy completamente seguro de cómo luego 'dispararía' el 'next'...?

¿Necesito modificar los datos dentro de allEventsObserve y de alguna manera simplemente seguir devolviéndolos? ¿Hago un nuevo observable (como se intentó arriba) con la carga correcta, y si es así, cómo lo activo? etc... He revisado aquí: Cómo declarar un observable en Angular2 pero no puedo seguir cómo se activa el 'segundo' observable. ¿Quizás tengo todo el paradigma equivocado?

Respuesta #1

Parece que estás malinterpretando lo que un operador de RxJS (como map, filter, etc.) realmente devuelve, y creo que corregir eso aclarará la solución.

Considera este breve ejemplo:

allEventsObserve
  .map(events => {
    return 'this was an event';
  })

Cierto, es un ejemplo bastante inútil ya que se pierde toda la información de events, pero ignoremos eso por ahora. El resultado del código anterior no es un arreglo de cadenas ni nada similar, en realidad es otro Observable. Este Observable simplemente emitirá la cadena 'this was an event' por cada arreglo de eventos que emita allEventsObserve. Esto es lo que nos permite encadenar operadores en los observables: cada operador en la cadena devuelve un nuevo Observable que emite elementos que han sido modificados de alguna manera por el operador anterior.

allEventsObserve 
  .map(events => {
    return 'this was an event';
  }) 
  .filter(events => typeof events !== 'undefined') 

allEventsObserve es obviamente un Observable, allEventsObserve.map() evalúa a un Observable, y lo mismo ocurre con allEventsObserve.map().filter().

Entonces, dado que esperas que tu función devuelva un Observable, no querrás llamar a subscribe todavía, ya que hacerlo devolvería algo que en realidad no es un Observable.

Con eso en mente, tu código puede reescribirse de la siguiente manera:

public getEvents(key:string,value:string) : Observable<Array<any>> {
    var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();

    return allEventsObserve 
      .map(events => {
          var match = events.filter(event => event[key] == value);
          if (match.length == 0) {
            throw 'no matching event found';
          } else {
            return match[0];
          }
      })
      .catch(e => {
        console.log(e);
        return e;
      });

}

Dado que getEvents devuelve un Observable, en otra parte de tu código harías algo como getEvents().subscribe(events => processEvents()) para interactuar con ellos. Este código también asume que this.getHTTPEvents() devuelve un Observable.

Además, nota que cambié tu bucle for por una llamada a filter, que opera sobre arreglos. events en este caso es un Array de JavaScript común, por lo que el filter que se está llamando no es el mismo filter que el operador filter de RxJS.

Artículos relacionados