0

I have a component that populates an Object array in its ngInit() method from a service which I then use the contents of in my HTML template.

My problem is I can use this data fine in the HTML template but if I try to use this same Object array in my TypeScript file I will get an undefined error.

Below is a simplified code example of my problem:

@Component({
  selector: 'booking',
  template: `  
    <div *ngFor="let r of requestedBookings">
      <label>Requested on {{r.created | date: 'd MMM H:mm'}}</label>
    </div>
  `
})

export default class BookingComponent {

    requestedBookings: Object[];

    constructor(private bookingService: BookingService) {
      
    }
    
    ngOnInit() {
      
      this.getRequestLog();
      
      // Cannot read property 'length' of undefined error
      // console.log(this.requestedBookings.length);
      
    }
    
   
    private getRequestLog(): void {
      
      this.bookingService.getRoomRequestBooking(1,1,1)
        .subscribe(data => this.requestedBookings = (data as any))
        .results, err => {
          console.log(err);
        }

}

Why is it in the above example I can use the requestedBookings array as expected in the HTML template but inside the TypeScript file I receive undefined errors?

6
  • 6
    Because getRequestLog is asynchronous, and thus this.requestedBookings.length is running before the bookings are returned. Commented Jul 26, 2017 at 2:32
  • @Rob is correct. requestedBookings: Object[]; is not an instantiation, it's a type declaration and is ambient. When ngOnInit runs, your variable is not instantiated. requestedBookings: Object[] = []; would allow the console log to execute. Commented Jul 26, 2017 at 2:35
  • So how can I ensure that getRequestLog() has completed before I access the array? Commented Jul 26, 2017 at 2:36
  • @ScottMackenzie Depends what you mean by completed, since you're subscribing to a stream, it may be constantly changing. You'll want to return the observable from getRequestLog and then subscribe to it to do your work. Alternatively, you could collapse the observable to a promise, return the promise, and then write something like getRequestLog().then(f => ...) Commented Jul 26, 2017 at 2:37
  • Thanks for your help identifying the asynchronous problem. I added an answer showing how I fixed my issue. Commented Jul 26, 2017 at 4:50

2 Answers 2

2

IMHO the correct way should be something like:

ngOnInit() {  
this.getRequestLog();
}

private getRequestLog(): void {
  this.bookingService.getRoomRequestBooking(1,1,1)
    .subscribe((data)=>{
     this.requestedBookings = data;
     console.log(this.requestedBookings.length);
    })
    .results, err => {
      console.log(err);
    }
}

As explained before, the call to getRoomRequestBooking is async, so you should not expect it will finish before calling the console.log. Instead, you should use the requestedBookings.length value in a place where you do know it will exist. Hope it helps!!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks I ended up doing very similar.
0

I fixed this issue by using this constructor from the subscribe method. the complete parameter event happens after successful completion.

  subscribe(next?: (value: T) => void, 
            error?: (error: any) => void, 
            complete?: () => void): Subscription;

Code is as follows:

ngOnInit() {  
  this.getRequestLog();
}

private getRequestLog() {

  this.bookingService.getRoomRequestBooking(this.date, this.level, this.room)
    .subscribe(
      data => this.requestedBookings = (data as any).results,
      err => {
        console.log(err);
      },
      () => console.log(this.requestedBookings.length));

}

2 Comments

could you check @DavidAlejandroReyesMilián answer ? which was answered 1hour before.
@k11k2 Yes not quite the same.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.