Angular может получить доступ к объекту, но не может прочитать свойство

#angular #observable #ngrx #ngrx-store

#angular #наблюдаемый #ngrx #ngrx-хранилище

Вопрос:

Я следил за настройкой, которая получает pages от родительского компонента и загружает его как *ngFor правильно, но как только я использую функцию для получения соответствующего названия книги {{ getBookName(id) }} , даже если она возвращает объект книги, не может найти его свойства, такие как book.name и возвращает 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. Вместо того, чтобы связывать функцию с шаблоном, попробуйте это. var book$ = this.store$.select(BookSelectors.selectBookById, id). и в шаблоне свяжите это наблюдаемое с помощью асинхронного канала. вот и все.

2. !!! он находится внутри *ngFor и использует different id в качестве второго аргумента, как мы можем отправлять динамические id s из шаблона в компонент без функции??

Ответ №1:

То, что вы пытаетесь сделать, недопустимо. вы пытаетесь подписаться на шаблон и сразу получаете значение, что не будет работать асинхронным / наблюдаемым способом. вместо этого вы должны использовать асинхронный канал с помощью angular. Вот так.

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

и в шаблоне просто используйте его так

 <button *ngFor="let page of pages">
  <div *ngIf="getBookName(page.book_id) | async as pageName">
    page {{ pageName }}
  </div>
</button>
 

Комментарии:

1. спасибо @waseem-rakab, но он вернул тот же результат, Cannot read property 'name' of undefined также попробовал другие свойства и получил ту же ошибку, как я уже упоминал, он может возвращать объект book как console.log , но не может возвращать определенное свойство внутри!

2. у шаблона нет проблем, и проблема возникает для .ts файла ERROR TypeError: Cannot read property 'name' of undefined at MapSubscriber.project (pages.component.ts:52) , вызывающего именно строку return book.name

3. спасибо @waseem-rakab, вы исправили эту ошибку

Ответ №2:

Попробуйте использовать асинхронный канал: | async

С помощью асинхронного канала вы указываете представлению, что значение получено из обещания, и значение может быть недоступно при рендеринге представления.

Другой подход может использоваться ? перед атрибутом, например object?.attribute