2

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?

1 Answer 1

1

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
     });
Sign up to request clarification or add additional context in comments.

2 Comments

thanks a lot, this really cleared it out so easy, now I understand why it was behaving like that. One little thing I still need to do a for loop after .map to iterate through each item in the list returned by firebase. for(let booking of bookings){ ....
@Xazzo good catch, I forgot to add that to my answer. updated

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.