3

I'm creating a little app which show books from a database. My project looks like this:

| presentational
| ---> book-card
| ---> book-details
| containers
| ---> book-item
| ---> books
| services
| ---> books

I use BookItemComponent as a container, this class communicates with BooksComponent. Books components show the books card (like bootstrap style), books-card component has input (get the book infos) and output (to show more actions and details about the book). Here is the code for these classes :

book-item.component.ts :

export class BookItemComponent implements OnInit {
  book: Book; /*= {"title": "New harry", "author": "J.K Rowling"};*/

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

  ngOnInit() {
    console.log("BookItemComponent.ngOnInit()");

    this.booksService.getBooks().subscribe(
      (books) => {
        const param = this.route.snapshot.params.id;
        console.log(param);
        this.book = books.find(book => book.id == param);
      });
  }
}


book-item.component.html :

<p>Book-item</p>
<p *ngIf="!book">No book found..</p>
<app-book-details
    [book]="book" 
    (addToFav)="onAddToFav(book)"></app-book-details>

The class BookItemComponent communicates with BooksComponent which is the main container :
books.component.ts

export class BooksComponent implements OnInit {

  books: Book[];

  constructor(private booksService: BooksService, private router: Router) { }

  ngOnInit() {
    this.booksService.getBooks().subscribe(books => {
      this.books = books;
    });
  }

  onShowDetails(event: Book) {
      this.router.navigate(['/', event.id]);
  }
}

And finally :
book-card.component.ts :

@Component({
  selector: 'app-book-card',
  templateUrl: './book-card.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrls: ['./book-card.component.css']
})
export class BookCardComponent {
  @Input() book: Book;
  @Output() showDetails = new EventEmitter<Book>();

  showBookDetails(event: Book) {
    if(event) {
      this.showDetails.emit(event);
    }    
  }
}

** PROBLEM ** : In the method BookItemComponent.ngOnInit(), when I use this :

this.booksService.getBooks().subscribe(
      (books) => {
        const param = this.route.snapshot.params.id;
        this.book = books.find(book => book.id == param);
      });

I can see that book is undefined, look at this picture:

enter image description here

  • Hello, is `books` from your subscription undefined (what do you get with a console.log)? – Wandrille Dec 28 '18 at 19:29
  • Welcome to stack overflow! The first step is to figure out *which* of the source values are undefined. As asked above, is `books` undefined? Is `param` undefined? Does your `book` have an `id` property? (The example shown in book-item.component.ts does not appear to?) If so, is the property numeric? Note that the parameter id will be a string so they won't match unless you coerce it to a number. – DeborahK Dec 28 '18 at 19:38

1 Answers1

1

Fixing book is undefined:

** PROBLEM ** : In the method BookItemComponent.ngOnInit(), when I use this :

[insert code block here]

I can see that book is undefined...

In your example you do not define book until OnInit. When the template is being rendered (in this case before your observable and set a value for book) it throws the error because book isn’t defined yet.

To fix this set public book: Book = {} as Book; where you declare it in your TS file.

And it wouldn’t hurt to set public books: Book[] = [];

Fixing the load error:

This took me quite some time and as a disclaimer I'm not an expert on AngularFire but in your books.service.ts you define books$ in your constructor. However, that was only called one time. In order for me to get your books to load I had to redefine the books$ variable. To make it easy I added a private function that you can reuse when you need to. Here is how the get books and the helper function look like:

  getBooks(): Observable<Book[]> {
    this.setBooks();
    console.log('BooksService.getBooks() called');
    return this.books$.pipe(catchError((error: any) => Observable.throw(error.json())));
  }

  private setBooks(): void {
    this.books$ = this.booksCollection.snapshotChanges().pipe(
      map(actions => actions.map(action => {
        const data = action.payload.doc.data() as Book;
        const id = action.payload.doc.id;
        return { id, ...data };
      }))
    );
  }
Community
  • 1
  • 1
Mitch Wilkins
  • 495
  • 4
  • 19
  • Hello, thanks fo the reply. If I initialize `book` and `books` there is no error anymore, great, but I still don't have the book loaded, no one found. I noticed that if I refresh the page, the book details are loaded. You can look at my code (I edited my post) – Seb Developer Dec 29 '18 at 09:08
  • So when you first load the page you do not see any books but when you refresh the page you see them? – Mitch Wilkins Dec 29 '18 at 17:34
  • Exactly. WhenI'm in the home page, I select a book : [Book selected image](https://ufile.io/mb8y1). But no book is found: [No book image](https://ufile.io/f5aki). And if I refresh this page, the book details are loaded ! [Book loaded image](https://ufile.io/d1kei) – Seb Developer Dec 30 '18 at 16:03
  • @SebDeveloper I updated my answer to help you fix the reload issue. – Mitch Wilkins Jan 01 '19 at 02:27
  • I looked at you reply and it seems to be the good solution! I haven't tested yet, but I will tell you quickly the result. Thanks a lot. – Seb Developer Jan 01 '19 at 21:32