Xazzo
Xazzo

Reputation: 85

Angular forEach Loop on returned data from observable (Firebase driven)

I have an observable from a service that returns an array of Payment objects, once I subscribe to my observable I perform a loop to add the amounts of each of the objects in the array. the problem I'm having that on the first load the loop is not being executed for what I assume is because the data has not yet arrived, however, if I trigger .next on the subject of the observable it workes fine.

The function of my component is like so:

paymentsSource = new Subject;
payments$ = this.paymentsSource.asObservable();
paymetsData:Payment[] = new Array();
totalPaymentsAmoutn:number = 0;

ngOnInit() {
  this.payments$
    .subscribe(
     (dates:{startDate:number, endDate:number}) => {
     //Here I need to performe some UTC and Locale conversion to dates thet are being passed on          
      };
      //Here I subscribe to my service          
      this.paymentService.getPaymentsByDateRange(dates.startDate, dates.endDate)
       .subscribe(
         payments => {
          this.totalPaymentsAmoutn = 0;
          this.paymetsData = payments;
          this.paymetsData.forEach( payment => {
            this.totalPaymentsAmoutn += payment.amount;
           })
          });      
       });


//this.paymentsStartDate is declear some where else but it does exist previous to this point
this.paymentsSource.next({startDate:this.paymentsStartDate, endDate:this.paymentsStartDate});

}

//This function I can triger manualy on the view
onPaymentDateRangeChanged($event){
  this.paymentsSource.next({startDate:$event.startDate, endDate:$event.endDate});
}

My Service function looks like this:

  getPaymentsByDateRange(startDate:number, endDate:number):Observable<Payment[]>{
    let paymentsArray:Payment[] = [];
    this.af.list('/bookings')
      .subscribe(
        (bookings) => {
          for(let booking of bookings){
            if(booking.payments){
              for(let payment of booking.payments){
                //condition for date range
                if(startDate <= payment.date && payment.date <= endDate){
                  payment.booking_key = booking.$key; 
                  paymentsArray.push(payment);
                }
              }
            }
          }
          //Sorting payments cronologicaly
          paymentsArray.sort(function(paymentA, paymentB){
            return paymentA.date - paymentB.date
          });
        }
      );
    return Observable.of(paymentsArray);
  }

When the page loads I do get the array back and the view gets populated but the value for this.totalPaymentsAmoutn remains 0, if I trigger the function manually it returns the array and updates the this.totalPaymentsAmoutn perfectly.

I am a bit new with Observables and I thought that once I subscribe to it if new data gets emitted It should run the script and update the data, I don't understand why on the first load is not working. I really think it has to do with not being able to perform the forEach loop since the array is still empty and I think that once the web sockets get connected consequent updates get push fast enough?

Upvotes: 2

Views: 22614

Answers (1)

LLai
LLai

Reputation: 13396

There is a disconnect in your getPaymentsByDateRange() method. You are returning the paymentsArray before it is populated.

getPaymentsByDateRange(startDate: number, endDate: number): Observable<Payment[]>{
    let paymentsArray: Payment[] = [];
    this.af.list('/bookings')
        .subscribe(...); // asynchronous
    // since this.af.list.subscribe is asynchronous, this returns before that call is finished.
    return Observable.of(paymentsArray);
}

You should instead return the af.list observable and subscribe only in the component. If you need to preprocess the data before the component uses it, you can use the rxjs .map operator

import 'rxjs/add/operator/map'; // import map operator

getPaymentsByDateRange(startDate: number, endDate: number): Observable<Payment[]>{
    let paymentsArray: Payment[] = []; // you may want to move this inside the map. If you don't you may get duplicates everytime firebase emits the bookings
    return this.af.list('/bookings')
        .map(bookings => {
            for(let booking of bookings){
                if(booking.payments){
                    for(let payment of booking.payments){
                        //condition for date range
                        if(startDate <= payment.date && payment.date <= endDate){
                            payment.booking_key = booking.$key; 
                            paymentsArray.push(payment);
                        }
                    }
                }
            }
            //Sorting payments cronologicaly
            paymentsArray.sort(function(paymentA, paymentB){
                return paymentA.date - paymentB.date
            });
            return paymentsArray;
        });
}

This ensures that when you subscribe to this method, the onNext callback will only fire once the af.list observable emits.

 this.paymentService.getPaymentsByDateRange(dates.startDate, dates.endDate)
     .subscribe(payments => {
         // fires when af.list observable emits
     });

Upvotes: 1

Related Questions