* ngFor не отображает данные, даже если данные обновляются - PullRequest
1 голос
/ 17 апреля 2020

У меня есть сервис для инъекций, который извлекает элементы корзины покупок:

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private readonly items: Array<{ product: IProduct, quantity: number }>
  private subject = new Subject<Array<{ product: IProduct, quantity: number }>>()

  constructor() {
    this.items = []
  }

  getItems() {
    return this.subject
  }

  addItem(product: { product: IProduct, quantity: number }) {
    const isProductExists = this.items.find((item => item.product._id === product.product._id))

    if (isProductExists) {
      isProductExists.quantity += product.quantity
    } else {
      this.items.push(product)
    }

    this.subject.next(this.items)
  }
}

В моем CartComponent я подписываюсь на items и повторяю их в *ngFor:

export class CartComponent implements OnInit {
  myItems: { product: IProduct, quantity: number }[]

  constructor(private cartService: CartService) {
  }

  ngOnInit(): void {
    this.cartService.getItems().subscribe(items => {
      this.myItems = items
    })
  }
}

Вот шаблон:

<div *ngFor="let item of myItems">{{item.product.name + ', ' + item.product.price }}</div>

Дело в том, что на странице ничего не отображается, хотя вызывается метод подписки, и я ясно вижу, что myItems обновление.

что я делаю не так в этом коде?

Ответы [ 2 ]

1 голос
/ 17 апреля 2020

Я добавил интерфейс OrderItem, чтобы немного упростить чтение:

export interface OrderItem {
  product: IProduct; 
  quantity: number;
}

Я обновил CartService для использования BehaviorSubject и держал orderItems как наблюдаемый, и изменил getItems для возврата наблюдаемый. Также измените addItem, чтобы обновить наблюдаемое:

export class CartService {
  private subject = new BehaviorSubject<OrderItem[]>([]);
  private orderItems$: Observable<OrderItem[]> = this.subject.asObservable();

  constructor() {
  }

  getItems(): Observable<OrderItem[]>{
    return this.orderItems$;
  }

  addItem(orderItem: OrderItem) {
    const orderItems = this.subject.getValue();
    const productIndex = orderItems.findIndex(item => item.product._id === orderItem.product._id);
    if(productIndex >= 0){
      const updatedOrderItem = orderItems[productIndex];
      updatedOrderItem.quantity +=1;
      const newOrderItems = orderItems.slice(0);
      newOrderItems[productIndex] = {
        ...orderItems[productIndex],
        ...updatedOrderItem
      }
    } else {
      orderItems.push(orderItem)
    }

    this.subject.next(orderItems);
  }

В компоненте корзины, просто возвращая наблюдаемое в сервисе:

export class CartComponent implements OnInit {
  myOrderItems$: Observable<OrderItem[]>;

  constructor(private cartService: CartService) { }

  ngOnInit() {
    this.myOrderItems$ = this.cartService.getItems();
  }

}

В HTML, простой ngFor и Asyn c pipe:

<table>
    <thead>
        <tr>
            <th>Product</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let item of myOrderItems$ | async">
            <td>{{item.product.name}}</td>
            <td>{{item.product.price | currency : 'GBP'}}</td>
            <td>{{item.quantity}}</td>
            <td>{{item.quantity * item.product.price | currency : 'GBP'}}</td>
        </tr>
    </tbody>
</table>

и в качестве простого теста в app.component:

export class AppComponent implements OnInit {
  name = 'Angular';

  constructor(private cartService: CartService){}

  ngOnInit(){
    this.addProduct1();
  }

  addProduct1(){
    let product1: IProduct = {_id:1, name: 'Product 1', price:25.00};
    let orderItem: OrderItem = { product: product1, quantity:1};
    this.cartService.addItem(orderItem);
  }

  addProduct2(){
    let product: IProduct = {_id:2, name: 'Product 2', price:15.00};
    let orderItem: OrderItem = { product: product, quantity:1};
    this.cartService.addItem(orderItem);
  }


}

и HTML:

<app-cart></app-cart>

<button (click)="addProduct1()">Add Product 1</button>
<button (click)="addProduct2()">Add Product 2</button>

Я создал рабочий пример для stackblitz: https://stackblitz.com/edit/angular-rxjs-shopping-cart-example

0 голосов
/ 17 апреля 2020

Чтобы не было утечек памяти и лучше очистить код, используйте asyn c pipe.

this.myItems$ = this.cartService.getItems();


<div *ngFor="let item of myItems$ | async">
  {{item.product.name }}, {{ item.product.price }}
</div>

вы подписываетесь на сервис в компоненте после того, как событие было отправлено в сервис. Почему это работает с BehaviorSubject? поскольку он запоминает последнее значение

Субъект

getItems  -----x----x->
component        ---x->

BehaviorSubject

getItems  -----x----x->
component        x--x->

, это правильно, чтобы вернуть asObservable ()

getItems() {
  return this.subject.asObservable()
}
...