1
votes

I have a model of subject events - defines what properties it should have:

export class EventSchema {
    title: string;
    start: string;
    end: string;
    price: number;

    constructor(title: string, start: string, end: string, price: number){
        this.title=title;
        this.start=start;
        this.end=end;
        this.price=price;
    }
}

I have another model that encapsulates the above model - has a subject name and the event properties in an array since there can be multiple events for a single subject:

import { EventSchema } from './eventSchema.model';

export class SubjectEvents {
    subjectName: string;
    eventData: EventSchema[];

    constructor(subjectName: string, eventData: EventSchema[]){
        this.subjectName=subjectName;
        this.eventData=eventData;
    }
}

I have a collection in firebase matching these models. So far there are only two documents, one with maths subjects and one with physics subjects, each with 2 events. Example data is: firebase data

I have this service which gets the data, and transforms it. In this instance, I want to take each event for a subject (document) and put it into one big array. Maybe I need to merge the output of each document... not sure how. I am able to achieve this somewhat - but the results are coming out duplicated, and I don't understand why. Please see below a screen shot of the final output.

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument, QuerySnapshot, QueryDocumentSnapshot } from 'angularfire2/firestore';
import { Observable, of, Subscription } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import 'rxjs/add/operator/mergeMap';
import { SubjectEvents } from '../_models/subjects/subjectEvents.model';
import { EventSchema } from '../_models/subjects/eventSchema.model';

@Injectable()
export class SubjectEventsService {

    subjectEventCollection : AngularFirestoreCollection<SubjectEvents>;
    subjectEventDocument : AngularFirestoreDocument<SubjectEvents>;
    subjectEvents : Observable<SubjectEvents[]>;
    subjectEvent : Observable<SubjectEvents>;
    filteredArr : Observable<any[]>;

    constructor(private afs : AngularFirestore) { 
      //Get user collection on initialise
      this.subjectEventCollection = this.afs.collection('subjectEvents');
    }
    getSubjectEvents(subjectFilter: string): Observable<SubjectEvents[]> {
      this.filteredArr = this.subjectEventCollection.snapshotChanges()
      .pipe(map(changes => {
        let filteredArr : any = [];
        console.log(filteredArr);
        return changes
            .map(action => {
              console.log(action);
              const data = action.payload.doc.data() as SubjectEvents;
              data.subjectName = action.payload.doc.data().subjectName;
              data.eventData = action.payload.doc.data().eventData;
              console.log(data);

              data.eventData.forEach(result => {filteredArr.push(result); console.log(filteredArr)});

              return filteredArr;
        });
      }));
      return this.filteredArr;
    }
}

Duplicated output

2
I would say you need to .pipe(mergeMap(changes... or return changes.pipe(mergeMap(action... because changes is an Observable<SubjectEvents[]> and you want to unwind that array to Observable<SubjectEvents>. It helps with reasoning if you type your parameters. (BTW perhaps bad form to mix pipable operators and non-pipable operators). - Richard Matsen

2 Answers

0
votes

Live example

I just came with a better approach, I created a custom operator called hasBeenEmitted, you just has to provide the property you want to filter upon.

const duplicateData = from(data);
const hasBeenEmitted = n => source =>
  new Observable(observer => {
    const emmited = [];
    return source.subscribe({
      next(item) {
        if (emmited.filter(e => e.title !== item.title).length === 0) {
          emmited.push(item);
          observer.next(item);
        }
      },
      error(err) { observer.error(err); },
      complete() { observer.complete(); }
    })
  });



const source = duplicateData.pipe(
  hasBeenEmitted(item => item.title),
  map(e => ({ value: e.price, start: e.start }))
);

source.subscribe(console.log);
0
votes

Live working example

I hope I can help, this is my propose: use filter and map, but in filter it uses a custom function to achieve your expected results:

const duplicateData = from(data);
const emmited = [];
const hasBeenEmitted = (item, emmited) => {
  if (emmited.filter(e => e.title !== item.title).length === 0) {
    emmited.push(item);
    return item;
  } else {
    return null;
  }
};

const source = duplicateData.pipe(
  filter(item => hasBeenEmitted(item, emmited)),
  map(e => ({ value: e.price, start: e.start }))
);

source.subscribe(console.log);