Wrong type of object after subscribe in RxJS

2019-09-19 12:24发布

I have an Angular 4 app.

I have a service that fetch data from Firebase database by API URL:

import { Product } from './../models/product';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AppSettings } from '../app-settings';

@Injectable()
export class ProductsService {

  products = [];

  constructor(private http: HttpClient) { }

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(AppSettings.DB_API_ENDPOINT + '/products.json',);
  }
}

And the component that displays that data:

import { Product } from './../../models/product';
import { Component, OnInit, Input } from '@angular/core';
import { ProductsService } from '../../services/products.service';
import { CategoriesService } from '../../services/categories.service';

@Component({
  selector: 'products-list',
  templateUrl: './products-list.component.html',
  styleUrls: ['./products-list.component.scss']
})
export class ProductsListComponent implements OnInit {

  products: Product[];
  numberOfProducts: number;
  page: number;

  constructor(private productsService: ProductsService, private categoriesService: CategoriesService) {
    this.page = 1;
    this.numberOfProducts = 0;
    this.products = [];
  }

  ngOnInit() {
    this.productsService.getProducts().subscribe(products => {
      console.log(products[0].getId());
      this.products.push(products[0] as Product);
      this.numberOfProducts = this.products.reduce((prev, el) => {
        return prev + el.qtyAvailable;
      }, 0);
    });
  }

  qtyChange(qty: number) {
    this.numberOfProducts -= qty;
  }

}

And the model Product:

export class Product {
    $key: string;
    qty: number;
    isSoldOut: boolean;

    constructor(
        private id: number,
        private name: string,
        private description: string,
        public qtyAvailable: number,
        private price: number,
        private image: string,
        public category: string
    ) {
        this.isSoldOut = false;
        this.qty = 0;
    }

    isAvailable() {
        return !this.isSoldOut;
    }

    getImageUrl() {
        return '/assets/images/products/' + this.image;
    }

    public getId(): number {
        return this.id;
    }

    getPrice() {
        return this.price;
    }

    getDescription() {
        return this.description;
    }

    getName(): string {
        return this.name;
    }
}

In the view of that component I use product.isAvailable() method from the model Product. I get th error message in console ERROR TypeError: _co.product.isAvailable is not a function. But when I type products[0].id in the service I get the error message while compile, that id is a private of Product. The list of producsts is displayed and paginated but without data, because of this error message.

2条回答
一纸荒年 Trace。
2楼-- · 2019-09-19 12:47

Here is a better way to organize this in Rxjs. This will handle everything inside your service mrthod:

getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(AppSettings.DB_API_ENDPOINT + '/products.json',)
      .switchMap(res => res); // make the retrieved list an Observable itself
      .map(product => new Product(product.name // etc)) // map every json object to an instance of your class
      .toArray(); // when the list is over, the Observable created from the json list will complete, and all of the emitted values will be stored in an array
}

For reference see these links: switchMap operator, toArray operator

This way is more cleaner, readable and in a more reactive style

查看更多
孤傲高冷的网名
3楼-- · 2019-09-19 12:54

I resolved my problem by using array syntax of JavaScript objects, to create the object of type Product inside subscribe method:

this.productsService.getProducts().subscribe(products => {
      this.products = products.map(p => new Product(p['id'], p['name'], p['description'], p['qtyAvailable'], p['price'],p['image'], p['category']));
      this.numberOfProducts = this.products.reduce((prev, el) => {
        return prev + el.qtyAvailable;
      }, 0);
    });

But still, I think it is redundant. Angular should automatically convert the type to Product[], should it?.

查看更多
登录 后发表回答