6
votes

I've been trying to use an array of products that's being returned by subscribing to the corresponding service. It works fine when it comes to displaying the objects properties on a list or something similar but I've been struggling to use the Material component data-table. I tried to do it by instantiating a new MatTableDataSource with the products array as an argument and to use try dynamic datasource by using the Observable directly but no success so far, the table is empty and the console is silent.

Here is the service :

import { IProduct } from './../interfaces/IProduct';
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: "root"
})
export class ProductService {
  productUrl = "https://localhost:44355/product/getproducts";

  constructor(private http: HttpClient) { }

  getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.productUrl);
  }

 }

The TypeScript file :

import { ProductService } from './../../services/product.service';
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { IProduct } from 'src/app/interfaces/IProduct';
import { Observable } from 'rxjs';
import { DataSource } from '@angular/cdk/collections';
import 'rxjs/add/observable/of';


@Component({
  selector: "app-product",
  templateUrl: "./product.component.html",
  styleUrls: ["./product.component.css"]
})
export class ProductComponent implements OnInit {
  public products = [];
  displayedColumns: string[] = ['productId', 'displayName', 'price', 'stockQuantity'];
  dataSource = new ProductDataSource(this.productService);

  constructor(private productService: ProductService) {}

  ngOnInit() {
    this.productService.getProducts()
      .subscribe(
      response => {
        this.products = response.items;
        console.log(this.products);
      }
    );
  }
}
export class ProductDataSource extends DataSource<any> {
  constructor(private productService: ProductService) {
    super();
  }
  connect(): Observable<IProduct[]> {
    return this.productService.getProducts();
  }
  disconnect() { }
}

And lastly the HTML

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!--- Note that these columns can be defined in any order.
        The actual rendered columns are set as a property on the row definition" -->
  <!-- Position Column -->
  <ng-container matColumnDef="productId">
    <th mat-header-cell *matHeaderCellDef> Id </th>
    <td mat-cell *matCellDef="let product"> {{product.productId}} </td>
  </ng-container>

  <!-- Name Column -->
  <ng-container matColumnDef="displayName">
    <th mat-header-cell *matHeaderCellDef> Nom </th>
    <td mat-cell *matCellDef="let product"> {{product.displayName}} </td>
  </ng-container>

  <!-- Weight Column -->
  <ng-container matColumnDef="price">
    <th mat-header-cell *matHeaderCellDef> Prix </th>
    <td mat-cell *matCellDef="let product"> {{product.price}} </td>
  </ng-container>

  <!-- Symbol Column -->
  <ng-container matColumnDef="stockQuantity">
    <th mat-header-cell *matHeaderCellDef> Quantité </th>
    <td mat-cell *matCellDef="let product"> {{product.stockQuantity}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Also I don't know if it's linked but at the subscription :

ngOnInit() {
        this.productService.getProducts()
          .subscribe(
          response => {
            this.products = response.items;
            console.log(this.products);
          }
        );

UPDATE Incoming Data from API :

{"items":[{"productId":1,"name":"B12BocalVerreSoufflé","description":"Bocal en verre soufflé à la main et son couvercle en liège. Très bon état.","price":13.00,"status":100,"stockQuantity":1,"displayName":"Bocal en verre soufflé                                                                                                                                                                                  ","productFlags":0},{"productId":2,"name":"B30AssietteCampagne","description":"Lot de 4 assiettes en céramique. Décor en fleurs fait à la main. Bon état.","price":16.00,"status":100,"stockQuantity":36,"displayName":"Assiettes campagne                                                                                                                                                                                      ","productFlags":1},{"productId":3,"name":"B41BocalPommeHenkel","description":"Bocal en verre et couvercle en plastique. Décor pomme rouge de la marque Henkel des années 70. Bon état.","price":200.00,"status":100,"stockQuantity":11,"displayName":"Bocal pomme rouge Henkel                                                                                                                                                                                ","productFlags":0}

And the interface IProduct :

export interface IProduct {
  name: string;

  price: number;

  productId: number;

  description: string;

  status: number;

  stockQuantity: number;

  displayName: string;

  productFlags: number;

  xmlData: string;

  blob: any;
}

UPDATE 2 : Stackblitz for the app : https://stackblitz.com/edit/angular-6xjfmf?embed=1&file=src/app/product.service.ts

FINAL UPDATE AND SOLUTION : So part of the solution was given by Deborah bellow, I had to use products as the datasource directly :

<table mat-table [dataSource]="products" class="mat-elevation-z8">

Also I still had the issue of not being able to get the items part of my response when subscribing, as it was considered an object and simply doing response.items wasn't working because items wasn't a property of Object.

The solution was easy I just had to do it in two steps :

ngOnInit() {
    this.productService.getProducts()
      .subscribe(
      response => {
        this.products = response;
        this.products = this.products.items;
        console.log(this.products);
      }
    );
  }

There you go , I hope it helps somebody in the same situation. Thanks to Deborah!

1

1 Answers

5
votes

This code:

  getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.productUrl);
  }

Is giving you back an Observable<IProduct[]>

UPDATE

If the response coming back from your server actually contains the product array under items, then obtain the products from the items in the service code:

  getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.productUrl)
       .pipe(
         tap(response => console.log(response)),
         map(response => response.items);
       )
  }

This should then return the items as the IProduct[]. You could also throw the tap operator in there temporarily just to confirm what your response looks like.

END UPDATE

So in your subscribe:

    this.productService.getProducts()
      .subscribe(
      response => {
        this.products = response.items;
        console.log(this.products);
      }
    );

response is of type IProduct[]. It has no items property.

Try changing the above code to this:

    this.productService.getProducts()
      .subscribe(
      products => {
        this.products = products;
        console.log(this.products);
      }
    );

And you should get your list of products.

Then I assume you need to set the datasource property to the returned list of products:

<table mat-table [dataSource]="products" class="mat-elevation-z8">

NOTE: All of the above ignores the dataSourceproperty in the class and your ProductDataSource class. If you can get the above to work, we can see how we can modify it to use your more generalized code.

This:

dataSource = new ProductDataSource(this.productService);

Will never retrieve any data because it does not subscribe. (Unless the mat-table has some magic that handles Observables and subscribes for you? No ... it doesn't. I just looked at the examples in the docs and they all subscribe manually.)