1

Here is my Books component that contains books array that I tried to console log. It logs an undefined but it shows correctly in DOM after using *ngFor.

import { Component, OnInit } from '@angular/core';
import { BooksService } from '../shared/books.service';
import { ActivatedRoute, Params } from '@angular/router';
import { Book } from './book.model';

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.scss']
})
export class BooksComponent implements OnInit {
  books: Book[];
  filteredBooks: Book[];
  id: string;

  constructor(
    private booksService: BooksService,
    private route: ActivatedRoute
  ) {}

  ngOnInit() {

    this.booksService.getJson()
      .subscribe(response => (
        this.books = response.json().books)
      );

      console.log(this.books) //undefined;

    this.route.params.subscribe((params: Params) => {
      this.id = params['id'];
    });
    const filter = this.books.filter(book => book.author === this.id);
    console.log(filter);
  }
}
codeepic
  • 3,723
  • 7
  • 36
  • 57
Ahmed Shaarawy
  • 49
  • 1
  • 3
  • 6
  • Are you expecting that your service will return value immediately? – yurzui Oct 08 '18 at 09:40
  • 1
    You are making an ajax call, in the subscribe write `console.log(this.books)`, then it will work but outside of subscribe it won't work – Sivakumar Tadisetti Oct 08 '18 at 09:40
  • 1
    Read about asynchronous code in JavaScript. You are making a service call which is asynchronous and assigning the response to `this.books` sometime once you get the response from server. At the same time browser will parse the line immediately which results in undefined – Gangadhar Jannu Oct 08 '18 at 10:21

3 Answers3

2

welcome to Stack Overflow! The problem I see in your code is that you're trying to print the books array before it has been fetched.

The function getJson(), you call from BooksService is an asynchronous call. Meaning, we don't know how long it might take before this function fetches the books array (might as well fail in some cases).

If you just want to print the list of books, you can do this (Note, I have added an error block to handle errors):

ngOnInit() {

  this.booksService.getJson()
    .subscribe(
      response => { // on success
        this.books = response.json().books)
        console.log(this.books); // shows your books!
      },
      error => { // on failure
        console.log('Error Occurred:' + JSON.stringify(error));
      }
    );
  // remaining code ...
}

Also in your template (html), you will have to add an *ngIf before iterating through these books:

<div *ngIf="books">
  <div *ngFor="let book of books">
    <div>{{book.name || 'unknown'}}</div>
  </div>
</div>

However, I strongly recommend you to read a little on promise chaining, Promise.race or even callback functions from one of these sources. You're free to refer elsewhere as well but the first site below (MDN) is a good place to refer anything related to javascript in my opinion :)

Hamzeen Hameem
  • 2,360
  • 1
  • 27
  • 28
1

You have a problem with this part of the code:

this.booksService.getJson()
  .subscribe(response => (
    this.books = response.json().books)
  );

  console.log(this.books) //undefined;

HTTP call is asynchronous so the code inside the .subscribe() will run after your console.log(this.books) //undefined;.

Put console.log inside .subscribe() method.

this.booksService.getJson()
      .subscribe(response => (
        this.books = response.json().books);
        console.log(this.books) //undefined;
      );

Actually - you are also reading the id from route.params - also an asynchronous task. In that case you should combine booksService.getJSON() stream with route.params stream and do something like that:

Observable.combineLatest(
    this.route.params,    //1st stream
    this.booksService.getJson().map(res => res.json().books) //2nd stream   
)
.do((values: any[]) => {
    const id = values[0]['id']; //params from 1st stream
    const allBooks = values[1];        //already mapped books from 2nd stream

    this.filteredBooks = allBooks.filter(book => book.author === id);

}).subscribe();
codeepic
  • 3,723
  • 7
  • 36
  • 57
0

Angular renders your books multiple times. It's undefined at first for angular too.

Try putting your filter code into your subscribe-Block

Nigel Nop
  • 180
  • 1
  • 6