0

I have followed setup which gets pages from parent component and loads it as *ngFor correctly, but once I use function to get related book name by {{ getBookName(id) }}, even though it returns the book object, can not find its properties like book.name and returns TypeError: Cannot read property 'name' of undefined


import { Component, OnInit, EventEmitter, Input };
import { Store } from '@ngrx/store';
import { AppStore } from '../shared/store.interface';
import * as BookSelectors from '../shared/book/book.selector';

@Component({
    selector: 'app-pages',
    template: `'
        <button *ngFor="let page of pages">
            page {{ page.number }} in {{ getBookName(page.book_id) }} <!-- page 32 in Book A -->
        </button>'`,
    styles: ['button{ border: 1px solid red }']
})
export class PagesComponent {

   @Input() pages: any[] = []; // comes from parent component

    constructor(private store$: Store<AppStore>) {}

    getBookName(id: number) {
        console.log(id) // 1
        this.store$.select(BookSelectors.selectBookById, id).subscribe(
            book => {
                console.log(book) // {id: 1, name: 'Book A', author: 'author A'} 
                return book.name // TypeError: Cannot read property 'name' of undefined
            }
        )
    }

}
  • 1
    Rather than binding the function with template try this. var book$ = this.store$.select(BookSelectors.selectBookById, id). and in the template bind this observable using async pipe. thats all. – Vimal Patel Nov 28 '20 at 17:08
  • !!! it is inside `*ngFor` and uses different `id` as second argument, how can we send dynamic `id`s from template to component without function?? – user14065257 Nov 29 '20 at 01:39

2 Answers2

2

What you’re trying to do is not valid. you’re trying to subscribe from the template and getting the value immediately, which will not work async/observable way. you should instead use the async pipe by angular instead. Like this.

getBookName(id: number) {
  return this.store$.select(BookSelectors.selectBookById, id).pipe(
    map((book) => {
      return book.name;
    })
  );

and in the template just use it like this

<button *ngFor="let page of pages">
  <div *ngIf="getBookName(page.book_id) | async as pageName">
    page {{ pageName }}
  </div>
</button>
waseemrakab
  • 315
  • 2
  • 9
  • thanks @waseem-rakab, but it returned same result, `Cannot read property 'name' of undefined` also tried other properties and get same error, as i mentioned it can return the book object as `console.log` but can not return a specific property inside! – user14065257 Nov 29 '20 at 01:27
  • the template has no problem and the issue comes forom `.ts` file `ERROR TypeError: Cannot read property 'name' of undefined at MapSubscriber.project (pages.component.ts:52)` exactly the line calling `return book.name` – user14065257 Nov 29 '20 at 01:32
  • thanks @waseem-rakab, your fixed this error – user14065257 Nov 29 '20 at 01:55
0

Try using the async pipe: | async

With the asynchronous pipe you’re indicating to the view that the value comes from a promise and the value may not be available when rendering the view.

Another approach could be using ? before the attribute like object?.attribute

jcobo1
  • 1,065
  • 1
  • 18
  • 34